Content that changes in place: the old one fades out as the new one arrives, as a fade, a rise or a pop.
$ pnpm dlx shadcn@latest add @dashral/swapInstallation
$ pnpm dlx shadcn@latest add @dashral/swapAdds the component, the components it builds on, its npm packages and the theme tokens it uses. Or by its full URL: https://dashral.com/r/swap.json
When to use
- Content that changes in place after an action: an icon turning into a check, a label going from Copy to Copied, a form giving way to a thank-you.
When not to use
- Content that appears or disappears, pushing what's below: Reveal.
- A number that counts to its new value: MetricCard, or Badge in the Motion edition.
Usage
value is what it shows now; when it changes, the old content leaves as the new one arrives.
"use client"
import { useState } from "react"
import { Check, Copy } from "lucide-react"
import { Button } from "@/components/dashral/button"
import { Swap } from "@/components/dashral/swap"
export default function CopyButton() {
const [copied, setCopied] = useState(false)
return (
<Button variant="outline" onClick={() => setCopied(true)}>
<Swap value={copied} effect="pop">
{copied ? <Check /> : <Copy />}
</Swap>
{copied ? "Copied" : "Copy"}
</Button>
)
}Using the npm package? Import from @dashral/ui/components/swap instead, here and in the examples; the rest is the same.
API reference
Swap
| Prop | Type | Default |
|---|---|---|
value | string | number | booleanRequired. What it shows now, as a key: a change swaps the old content out and the new one in. | — |
children | ReactNodeThe content for this value. | — |
effect | "fade" | "rise" | "pop"How the new content arrives: fading, rising a few pixels (text, forms) or popping in from small (an icon such as a check). | "fade" |
wait | booleanThe old content leaves before the new one comes, rather than both at once. | false |
appear | booleanThe first content arrives with the effect too, as it's added. | false |
as | "span" | "div"The element: span inside a line or a button, div for blocks. | "span" |
className | stringClasses of each content's own element, e.g. its layout. | — |
Accessibility
- The content on its way out is hidden from screen readers (aria-hidden) and can't be clicked, so only the new one is read and used.
Motion
- The old content fades out fast (--duration-fast, --ease-exit) while the new one arrives (--duration-standard, --ease-enter), both in the same place, so nothing blinks.
- With reduced motion they only cross-fade.