Skip to main content
WireKit
Copy for LLM

Swap

Two icons, one slot, a transition between them. A sun and a moon, a hamburger and a close, a play and a pause.

swap holds no state of its own — you point it at something you already have. Two components arguing about who is open is worse than one that simply renders what it is told.

The three swap effects

Usage

Give it an on slot, an off slot, and an expression naming the state:

<div x-data="{ open: false }">
    <x-wirekit::button x-on:click="open = !open">
        <x-wirekit::swap effect="rotate" expression="open">
            <x-slot:on><x-wirekit::icon name="x-mark" /></x-slot:on>
            <x-slot:off><x-wirekit::icon name="bars-3" /></x-slot:off>
        </x-wirekit::swap>
    </x-wirekit::button>
</div>

expression is an Alpine expression, evaluated in whatever scope surrounds the swap. It needs no x-data of its own — Alpine resolves the name up the scope chain, so open here is the button's open.

expression is what makes it swap. active alone cannot active is a PHP value, resolved once when the page renders. A swap given only active is frozen at whatever the server thought and never moves again — which is an icon, not a swap.

Use active only for a swap the server already knows the answer to and that never changes on the client. For anything a reader can toggle, you must use expression.

Effects

effect What happens
fade Crossfade. The quiet default.
rotate Half a turn — reads well for a sun/moon or a chevron.
flip A card flip on the Y axis.

Props

Prop Type Default Description
expression string|null null Alpine expression naming the state. Required for a swap that changes on the client.
active bool false Static state, for a server-rendered swap that never changes. Ignored when expression is set.
effect string 'fade' fade, rotate, flip.
scope string|null null Class-scope override.

Slots

Slot Description
on Shown when active is true.
off Shown when active is false. Falls back to the default slot.

Accessibility

Both children stay in the DOM — you cannot crossfade between an element and nothing. That has one consequence worth knowing about: the hidden child is aria-hidden, so a screen reader reads only the state that is showing. Without that, every toggle would announce both states ("sun moon").

The swap itself is not a control and takes no focus. Put it inside a real <button> and give that the accessible name — a swap between two icons has no text for a screen reader to read.

Under prefers-reduced-motion: reduce the state still changes; it just arrives instead of traveling. The transform is dropped too, not only the transition — a half-turn that snaps is still a half-turn nobody asked for.

Keyboard Interaction

None of its own. Whatever you wrap it in keeps its normal keyboard behavior.