Swap

Cross-fades (or rotates/flips) between two icons on toggle.

Transition variants

Real use · controlled mobile menu toggle

Menu is closed

Using Swap

Silica ships one design system through three paths. They share a class vocabulary but are not interchangeable — pick the one that matches what you are building.

Plain HTML — CSS classes

The Tailwind v4 plugin. Works in any framework or none — Rails, Django, PHP, Go, a static file. This path is complete for Swap — it needs no JavaScript, so the classes below are the whole story.

/* in your CSS */
@import "tailwindcss";
@plugin "@wizeworks/silicaui";

Root class swap, with 4 modifiers:

  • .swap
  • .swap-flip
  • .swap-off
  • .swap-on
  • .swap-rotate

React

Typed components over those same classes, with behavior and accessibility from Base UI.

npm i @wizeworks/silicaui-react
import { Swap } from "@wizeworks/silicaui-react";

Props

PropTypeRequiredDescription
onReact.ReactNodeyesShown when active (checked).
offReact.ReactNodeyesShown when inactive.
activebooleannoControlled active state.
defaultActivebooleannoInitial state when uncontrolled.
onActiveChange(active: boolean) => voidnoCalled when toggled.
variantSwapVariantnoTransition style. fade (default), rotate, or flip.
labelstringnoAccessible label.

Source of the demo above

The exact file that renders the examples above, so it can never drift from them. It imports a few of this site’s own layout helpers (../lib/…), so read it for the component calls rather than copying it whole.

Show source
import { useState } from "react";
import { Swap } from "@wizeworks/silicaui-react";
import { Section, Row } from "../lib/Section";

function MenuIcon() {
    return (
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" width="28" height="28">
            <path d="M4 6h16M4 12h16M4 18h16" strokeLinecap="round" />
        </svg>
    );
}
function CloseIcon() {
    return (
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" width="28" height="28">
            <path d="M6 6l12 12M18 6 6 18" strokeLinecap="round" />
        </svg>
    );
}
function SunIcon() {
    return (
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" width="24" height="24">
            <circle cx="12" cy="12" r="4" />
            <path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4" strokeLinecap="round" />
        </svg>
    );
}
function MoonIcon() {
    return (
        <svg viewBox="0 0 24 24" fill="currentColor" width="24" height="24">
            <path d="M21 12.8A9 9 0 1 1 11.2 3 7 7 0 0 0 21 12.8Z" />
        </svg>
    );
}

export function SwapDemo() {
    const [menuOpen, setMenuOpen] = useState(false);

    return (
        <>
            <Section title="Transition variants">
                <Row>
                    <Swap variant="fade" on={<CloseIcon />} off={<MenuIcon />} label="Fade" />
                    <Swap variant="rotate" on={<CloseIcon />} off={<MenuIcon />} label="Rotate" />
                    <Swap variant="flip" on={<MoonIcon />} off={<SunIcon />} label="Flip" />
                </Row>
            </Section>

            <Section title="Real use · controlled mobile menu toggle">
                <div className="flex items-center gap-3">
                    <Swap
                        variant="rotate"
                        active={menuOpen}
                        onActiveChange={setMenuOpen}
                        on={<CloseIcon />}
                        off={<MenuIcon />}
                        label="Toggle menu"
                    />
                    <span className="text-sm opacity-70">
                        Menu is {menuOpen ? "open" : "closed"}
                    </span>
                </div>
            </Section>
        </>
    );
}

Vanilla — node tree to HTML

Swap — a hidden checkbox driving a pure-CSS cross-fade between two children (.swap-on / .swap-off, authored by the host as the node's children). Build a node tree, project it with toHtml(), and let the zero-dependency runtime hydrate it. This is the path for generated or user-authored documents — a site builder, CMS output, a static export.

npm i @wizeworks/silicaui-html @wizeworks/silicaui-behaviors
import { atom, toHtml } from "@wizeworks/silicaui-html";

const node = atom("Swap", "swap", { /* props */ }, [/* children */]);
const markup = toHtml(node);

Styling on this path is the class string in the second argument — the same classes as the Plain HTML section above. There is no color prop here; write "swap swap-primary" rather than passing a colour as a prop.

Takes children
yes
Hydrated by
nothing — this one is static markup

The full API for Swap, on every path, is available through the Silica MCP server.