Swap
Cross-fades (or rotates/flips) between two icons on toggle.
Transition variants
Real use · controlled mobile menu toggle
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-reactimport { Swap } from "@wizeworks/silicaui-react";Props
| Prop | Type | Required | Description |
|---|---|---|---|
| on | React.ReactNode | yes | Shown when active (checked). |
| off | React.ReactNode | yes | Shown when inactive. |
| active | boolean | no | Controlled active state. |
| defaultActive | boolean | no | Initial state when uncontrolled. |
| onActiveChange | (active: boolean) => void | no | Called when toggled. |
| variant | SwapVariant | no | Transition style. fade (default), rotate, or flip. |
| label | string | no | Accessible 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-behaviorsimport { 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.