Toggle and toggle group
FreeA button that stays pressed, alone or in a raised group that holds one choice or many and moves with the arrow keys as a single tab stop.
pnpm dlx shadcn@latest add https://caliperui.site/r/toggle.jsonWhen to use #
Use it for
- Bold, italic, pin, mute: a setting that is on or off right now.
- A small set of related options, such as alignment or view.
Skip it when
- Choosing a page or a mode with a visible result; use tabs or a segmented control.
- Yes or no in a form; use a switch.
Decisions #
Why it is built this way, so you know what to keep when you change your copy.
Behaviour
- One tab stop, arrows move
- A group of twelve tools costs one Tab press. Arrow keys follow the reading direction, so it works right to left; in single mode the arrow also selects.
Styling
- On is recessed, never raised
- A pressed toggle sinks into the shell, the way a toggled tool does in a button group, so it never reads like the selected plate of a segmented control.
Usage
- Single always keeps one selected
- Single behaves like a radio group. If people need to turn everything off, use multiple.
Installation #
pnpm dlx shadcn@latest add https://caliperui.site/r/toggle.json
Adds its 5 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.
Usage #
This is the code behind the preview above. It is built from Toggle, ToggleGroup and ToggleGroupItem.
import { AlignCenter, AlignLeft, AlignRight, Bold, Italic, Pin, Underline } from "lucide-react" import { useState } from "react" import { Toggle, ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle" export function ToggleDemo() { const [style, setStyle] = useState(["bold"]); const [align, setAlign] = useState(["left"]); return ( <div className="grid justify-items-start gap-6"> <div className="flex flex-wrap items-center gap-2"> <Toggle size="icon" aria-label="Bold"><Bold aria-hidden="true" /></Toggle> <Toggle defaultPressed><Pin aria-hidden="true" />Pinned</Toggle> <Toggle size="icon" aria-label="Underline" disabled><Underline aria-hidden="true" /></Toggle> </div> <ToggleGroup label="Text style" value={style} onValueChange={setStyle}> <ToggleGroupItem value="bold" size="icon" aria-label="Bold"><Bold aria-hidden="true" /></ToggleGroupItem> <ToggleGroupItem value="italic" size="icon" aria-label="Italic"><Italic aria-hidden="true" /></ToggleGroupItem> <ToggleGroupItem value="underline" size="icon" aria-label="Underline"><Underline aria-hidden="true" /></ToggleGroupItem> </ToggleGroup> <ToggleGroup type="single" label="Alignment" value={align} onValueChange={setAlign}> <ToggleGroupItem value="left"><AlignLeft aria-hidden="true" />Left</ToggleGroupItem> <ToggleGroupItem value="center"><AlignCenter aria-hidden="true" />Center</ToggleGroupItem> <ToggleGroupItem value="right"><AlignRight aria-hidden="true" />Right</ToggleGroupItem> </ToggleGroup> </div> ); }
API reference #
Toggle
component
Also accepts the native props of its root element and a ref.
ToggleGroup
component
Also accepts the native props of its root element and a ref.
ToggleGroupItem
component
Also accepts the native props of its root element and a ref.
Accessibility #
It is built on native elements and keeps aria-pressed and aria-label in step with state.
Motion #
It declares no animation in its component source. Any transition comes from its stylesheet, which uses the duration tokens that collapse to instant under prefers-reduced-motion.