Toggle
A toggle is a button that stays pressed until it is pressed again. Use it for something like bold or mute that someone switches on and off.
import { Bold, Italic, VolumeX } from "lucide-react";
import { Toggle } from "@/components/ui/toggle";
export default function ToggleDemo() {
return (
<>
<Toggle size="icon" aria-label="Bold">
<Bold />
</Toggle>
<Toggle size="icon" aria-label="Italic" defaultPressed>
<Italic />
</Toggle>
<Toggle>
Mute
<VolumeX />
</Toggle>
<Toggle defaultPressed>
Mute
<VolumeX />
</Toggle>
<Toggle disabled>Disabled</Toggle>
</>
);
}The code
The component is a single file, read from the repository when the site is built. Press Show code to see all of it.
import type { ComponentProps } from "react";
import { Toggle as BaseToggle } from "@base-ui/react/toggle";
import { cx } from "@/lib/cx";
import { focusRing } from "@/lib/styles";
type Size = "default" | "icon";
// A button that stays pressed. Pressed uses the accent. Hover and pressed are guarded with
// not-data-[disabled], and the scale-down on press is skipped for reduced motion, as in Button.
const base = [
"inline-flex h-9 cursor-pointer items-center justify-center gap-2 rounded-md border border-text-secondary text-base font-semibold text-text",
"[&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0",
"transition-[background-color,border-color,color,scale] duration-150 ease-out",
focusRing,
"motion-safe:not-data-[disabled]:active:scale-97",
"not-data-[disabled]:not-data-[pressed]:hover:bg-text/10",
"not-data-[disabled]:data-[pressed]:border-accent not-data-[disabled]:data-[pressed]:bg-accent/10 not-data-[disabled]:data-[pressed]:text-accent",
"not-data-[disabled]:data-[pressed]:hover:bg-accent/20",
"data-[disabled]:cursor-not-allowed data-[disabled]:border-border data-[disabled]:bg-surface data-[disabled]:text-text-secondary",
].join(" ");
const sizes: Record<Size, string> = {
default: "px-4",
icon: "w-9",
};
export function Toggle({
size = "default",
className,
...props
}: Omit<ComponentProps<typeof BaseToggle>, "className"> & { size?: Size; className?: string }) {
return <BaseToggle className={cx(base, sizes[size], className)} {...props} />;
}Props
| Prop | Type | Default | What it does |
|---|---|---|---|
size | default | icon | default | icon makes a square toggle for an icon with no label. |
defaultPressed | boolean | false | Whether it starts pressed, when you let it manage its own state. |
pressed | boolean | — | Whether it is pressed, when you control it yourself. |
onPressedChange | function | — | Called with the new value when the toggle is pressed. |
disabled | boolean | false | Turns the toggle off. |
className | string | — | Extra classes, added after the toggle's own. |
Every other prop goes to the Base UI Toggle underneath.
The thinking
A toggle sits between a button and a switch: it looks like a button, and it remembers whether it is on.
- Built on Base UI: It reports its pressed state to assistive tech, and is operated with the keyboard like any button.
- Match Button: The same height, radius, text size and press animation as Button, so the two sit side by side in a toolbar.
- Choose by effect: A toggle suits an action that shows its state on a button face, like Bold. For a setting in a list, use a switch.
Interface design
- Size: 36px tall with 16px of side padding. The icon toggle is a 36px square. Corners use the md radius, 6px.
- Off: A 1px outline in the secondary text color and the main text color for the label.
- Pressed: An accent outline, a 10% accent fill and an accent label, 14.6:1 against the page.
- Disabled: A surface fill and a quiet label. A disabled toggle that is pressed looks the same as one that isn't.
Interaction
- Press it: Click it, or press Enter or Space when it has focus. Each press flips it.
- Hover: An unpressed toggle gets a 10% wash. A pressed one deepens its accent fill to 20%.
- While you press: The toggle shrinks slightly, to 97%, over 150ms. People who ask for reduced motion don't get the shrink.
- Focus and disabled: Keyboard focus draws a 2px outline, 2px away. A disabled toggle doesn't react to hover or press.
Empathy
- Announced: A screen reader says it is a toggle button and whether it is pressed.
- Keep the name fixed: Don't change the label when it is pressed. Bold stays Bold, and the pressed state tells people it is on. Give icon toggles an
aria-label, as the demo does. - Color isn't the only signal: The pressed look is a color change, so the pressed state carries the information for anyone who can't see it.
- Target size: 36px tall clears the 24px minimum of WCAG 2.2 AA.