Toggle Group
A set of toggle buttons for choosing one or several options from a small, visible set.
import {
AlignCenterIcon,
AlignJustifyIcon,
AlignLeftIcon,
AlignRightIcon,
} from "lucide-react"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
export default function ToggleGroupDemo() {
return (
<ToggleGroup
variant="outline"
spacing={0}
defaultValue={["left"]}
aria-label="Text alignment"
>
<ToggleGroupItem value="left" aria-label="Align left">
<AlignLeftIcon />
</ToggleGroupItem>
<ToggleGroupItem value="center" aria-label="Align center">
<AlignCenterIcon />
</ToggleGroupItem>
<ToggleGroupItem value="right" aria-label="Align right">
<AlignRightIcon />
</ToggleGroupItem>
<ToggleGroupItem value="justify" aria-label="Justify">
<AlignJustifyIcon />
</ToggleGroupItem>
</ToggleGroup>
)
}Anatomy
- 1GroupOne control and one tab stop: arrow keys move between items, Tab moves on. It sets the variant, size and spacing for every item.
- 2Pressed itemSelected options take the pressed fill and report aria-pressed. A group can require one choice or allow several.
- 3ItemA toggle button. Icon-only items need an aria-label, like any icon button.
Installation
$ pnpm dlx shadcn@latest add https://www.prfct.dev/r/toggle-group.jsonUsage
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"<ToggleGroup defaultValue={["left"]} aria-label="Text alignment">
<ToggleGroupItem value="left" aria-label="Align left">
<AlignLeftIcon />
</ToggleGroupItem>
<ToggleGroupItem value="center" aria-label="Align center">
<AlignCenterIcon />
</ToggleGroupItem>
</ToggleGroup>The value is always an array of the pressed items' values — ["left"] for a single choice, ["bold", "italic"] for several.
Examples
Multiple
Add multiple to let any number of items be pressed at once, as in a formatting toolbar.
import {
BoldIcon,
ItalicIcon,
StrikethroughIcon,
UnderlineIcon,
} from "lucide-react"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
export default function ToggleGroupMultiple() {
return (
<ToggleGroup
multiple
defaultValue={["bold", "italic"]}
aria-label="Text style"
>
<ToggleGroupItem value="bold" aria-label="Bold">
<BoldIcon />
</ToggleGroupItem>
<ToggleGroupItem value="italic" aria-label="Italic">
<ItalicIcon />
</ToggleGroupItem>
<ToggleGroupItem value="underline" aria-label="Underline">
<UnderlineIcon />
</ToggleGroupItem>
<ToggleGroupItem value="strikethrough" aria-label="Strikethrough">
<StrikethroughIcon />
</ToggleGroupItem>
</ToggleGroup>
)
}Segmented
spacing={0} with the outline variant fuses the items into one control with shared borders — a segmented control for switching between a few views of the same content.
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
export default function ToggleGroupSegmented() {
return (
<ToggleGroup
variant="outline"
size="sm"
spacing={0}
defaultValue={["week"]}
aria-label="Calendar range"
>
<ToggleGroupItem value="day">Day</ToggleGroupItem>
<ToggleGroupItem value="week">Week</ToggleGroupItem>
<ToggleGroupItem value="month">Month</ToggleGroupItem>
<ToggleGroupItem value="year">Year</ToggleGroupItem>
</ToggleGroup>
)
}View switcher
In a single-choice group, pressing the active item again unpresses it and leaves nothing selected. When one option must always be active, control the value and ignore empty updates.
- Atlas
- Beacon
- Cinder
- Delta
"use client"
import * as React from "react"
import { LayoutGridIcon, ListIcon } from "lucide-react"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
import { cn } from "@/lib/utils"
const projects = ["Atlas", "Beacon", "Cinder", "Delta"]
export default function ToggleGroupViewSwitcher() {
const [view, setView] = React.useState("grid")
return (
<div className="flex w-full max-w-sm flex-col gap-4">
<div className="flex items-center justify-between">
<span id="view-label" className="text-sm font-medium">
Projects
</span>
<ToggleGroup
variant="outline"
size="sm"
spacing={0}
value={[view]}
// Single selection: ignore attempts to deselect the active view.
onValueChange={(value) => value.length > 0 && setView(value[0])}
aria-labelledby="view-label"
>
<ToggleGroupItem value="grid" aria-label="Grid view">
<LayoutGridIcon />
</ToggleGroupItem>
<ToggleGroupItem value="list" aria-label="List view">
<ListIcon />
</ToggleGroupItem>
</ToggleGroup>
</div>
<ul
className={cn(
"grid gap-2",
view === "grid" ? "grid-cols-2" : "grid-cols-1"
)}
>
{projects.map((project) => (
<li
key={project}
className={cn(
"rounded-lg border bg-card px-3 text-sm shadow-raised",
view === "grid"
? "flex h-16 items-end pb-2"
: "flex h-10 items-center"
)}
>
{project}
</li>
))}
</ul>
</div>
)
}const [view, setView] = React.useState("grid")
<ToggleGroup
value={[view]}
onValueChange={(value) => value.length > 0 && setView(value[0])}
aria-label="View"
>
…
</ToggleGroup>Vertical
orientation="vertical" stacks the items and switches keyboard navigation to the up and down arrows — the layout of a tool palette.
import {
MousePointer2Icon,
PenToolIcon,
SquareIcon,
TypeIcon,
} from "lucide-react"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
export default function ToggleGroupVertical() {
return (
<ToggleGroup
orientation="vertical"
defaultValue={["select"]}
aria-label="Tools"
className="rounded-xl border bg-card p-1 shadow-raised"
>
<ToggleGroupItem value="select" aria-label="Select">
<MousePointer2Icon />
</ToggleGroupItem>
<ToggleGroupItem value="pen" aria-label="Pen">
<PenToolIcon />
</ToggleGroupItem>
<ToggleGroupItem value="shape" aria-label="Rectangle">
<SquareIcon />
</ToggleGroupItem>
<ToggleGroupItem value="text" aria-label="Text">
<TypeIcon />
</ToggleGroupItem>
</ToggleGroup>
)
}Sizes
size applies to every item: sm 32, default 36, lg 40.
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
const sizes = ["sm", "default", "lg"] as const
export default function ToggleGroupSizes() {
return (
<div className="flex flex-col items-center gap-4">
{sizes.map((size) => (
<ToggleGroup
key={size}
variant="outline"
size={size}
spacing={0}
defaultValue={["monthly"]}
aria-label={`Billing period (${size})`}
>
<ToggleGroupItem value="monthly">Monthly</ToggleGroupItem>
<ToggleGroupItem value="yearly">Yearly</ToggleGroupItem>
</ToggleGroup>
))}
</div>
)
}Guidelines
When to use
- To switch between two to five views or modes of the same content: Day · Week · Month, grid or list.
- For toolbars with related options, single-choice (alignment) or multiple-choice (text style).
When not to use
- For choices inside a form that is submitted later — use a Radio Group, which reads as a form field and supports descriptions.
- For more than five or six options, or long labels — use a Select.
- For unrelated actions — use a Button Group. A toggle group implies a shared state.
- For switching between views that each have their own panel — use Tabs. For navigating between pages, use links.
Keep labels short and parallel
Segments should be the same kind of thing and roughly the same length, so the control reads as a set. Icons work well in toolbars; words work better for modes.
Accessibility
- The group renders
role="group"; name it witharia-labeloraria-labelledbypointing at a visible label. - Each item is a toggle button with
aria-pressed. Icon-only items need anaria-label. - The group is a single tab stop with roving focus: Tab enters it, arrow keys move between items, and focus wraps around at the ends (
loopFocus).
| Key | Behavior |
|---|---|
Tab | Moves focus into the group; press again to leave it. |
→↓ | Moves focus to the next item (down in vertical groups). |
←↑ | Moves focus to the previous item (up in vertical groups). |
SpaceEnter | Presses or unpresses the focused item. |
API reference
ToggleGroup
Renders a <div> with role="group". Accepts every prop of Base UI's Toggle Group.
ToggleGroupItem
A Toggle that reports its value to the group. Renders a <button>.