Skip to content

shadcn.io is an independent project built on shadcn/ui

Components

Free

Swap for Radix UI

Toggle between two icons or states with a fade, blur, flip, rotate, or scale. For React, when a sun becomes a moon or a play button turns to pause.

Radix UI does not include this primitive, so this page uses the Ark UI component. The visuals match the Ark UI version.

Installation

Sign in to get your install command
Free with an account. It carries your token, so the CLI installs everything in one go.

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.

Can I read the swap state from inside?

Yes. The file exports useSwap, Ark UI's swap context hook, for a child that needs to know whether it is the on or off side.

How do I install Swap in React?

Run the command in the Installation section on this page. It adds the swap file, built on Ark UI, and uses tailwind-variants for the variants.

API Reference

Swap

<span> data-slot="swap"

PropType
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

Merge props onto the child element instead of rendering a host.

className?string

Merged with the part's classes.

SwapIndicator

<span> data-slot="swap-indicator"

PropType
type"on" | "off"

Which state it's for: on shows while on, off while off.

Type
"on" | "off"
asChild?boolean

Merge props onto the child element instead of rendering a host.

useSwap

PropType
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