shadcn.io is an independent project built on shadcn/ui
Installation
Usage
import { MoonIcon, SunIcon } from "lucide-react";
import React from "react";
import { Button } from "@/components/ui/button";
import { Swap, SwapIndicator } from "@/components/ui/swap";const SwapDemo = () => {
const [swap, setSwap] = React.useState(false);
return (
<Button onClick={() => setSwap(!swap)} size="icon-lg" variant="outline">
<Swap swap={swap} variant="fade">
<SwapIndicator type="on">
<MoonIcon />
</SwapIndicator>
<SwapIndicator type="off">
<SunIcon />
</SwapIndicator>
</Swap>
</Button>
);
};Accessibility
- Enter
- Activate the focused control.
- Space
- Activate the focused control.
Demos
Blur
Flip
Rotate
Scale
FAQ
Which transitions can Swap use?
Five: fade (the default), blur, flip, rotate and scale. Pass variant on Swap. Each has its own enter and exit animation, shown in the demos on this page.
How do I put two icons in a Swap?
Wrap them in SwapIndicator with type="on" and type="off", and control the state with the swap prop on Swap. When swap is true the "on" indicator shows.
Does Swap respect reduced motion?
Yes. The indicators carry motion-reduce:animate-none, so with the system's reduced-motion setting on, the icon changes without animation.
How do I make a Swap button accessible?
Swap draws the icons only, so the control around it needs a name. Put it in a button with an aria-label, and use aria-pressed if it is a toggle. Keyboard use comes from that button: Enter and Space.
Are both icons in the page at once?
Not by default. lazyMount and unmountOnExit default to true, so an indicator is mounted when it is shown and unmounted after its exit animation.
API Reference
Swap
<span> data-slot="swap"
hideMode?"display-none" | "activity"
How to hide content when mounted but not present.
- display-none: HTML hidden attribute. Effects stay alive.
- activity: React 19 <Activity mode="hidden">. Effects pause. Requires React 19+.
- Type
"display-none" | "activity"- Default
"display-none"
lazyMount?boolean
Whether to enable lazy mounting. Ark default: false.
- Default
true
swap?boolean
Whether the swap is in the "on" state.
- Default
false
unmountOnExit?boolean
Whether to unmount on exit. Ark default: false.
- Default
true
variant?"blur" | "fade" | "flip" | "rotate" | "scale"
The visual style.
- Type
"blur" | "fade" | "flip" | "rotate" | "scale"- Default
"fade"
asChild?boolean
Use the provided child element as the default rendered element, combining their props and behavior.
className?string
Merged with the part's classes.
SwapIndicator
<span> data-slot="swap-indicator"
type"on" | "off"
Which state it's for: on shows while on, off while off.
- Type
"on" | "off"
asChild?boolean
Use the provided child element as the default rendered element, combining their props and behavior.
useSwap
hideMode?"display-none" | "activity"
How to hide content when mounted but not present.
- display-none: HTML hidden attribute. Effects stay alive.
- activity: React 19 <Activity mode="hidden">. Effects pause. Requires React 19+.
- Type
"display-none" | "activity"- Default
"display-none"
lazyMount?boolean
Whether to enable lazy mounting.
- Default
false
swap?boolean
Whether the swap is in the "on" state.
- Default
false
unmountOnExit?boolean
Whether to unmount on exit.
- Default
false