Toggle Group
A set of two-state buttons that can be toggled on or off.
import { Bold, Italic, Underline } from 'lucide-react'
import { ToggleGroup, ToggleGroupItem } from 'ui'
export function ToggleGroupDemo() {
return (
<ToggleGroup type="multiple">
<ToggleGroupItem value="bold" aria-label="Toggle bold">
<Bold className="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="italic" aria-label="Toggle italic">
<Italic className="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="underline" aria-label="Toggle underline">
<Underline className="h-4 w-4" />
</ToggleGroupItem>
</ToggleGroup>
)
}Installation
npx shadcn-ui@latest add toggle-groupUsage
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'<ToggleGroup type="single">
<ToggleGroupItem value="a">A</ToggleGroupItem>
<ToggleGroupItem value="b">B</ToggleGroupItem>
<ToggleGroupItem value="c">C</ToggleGroupItem>
</ToggleGroup>Examples
Default
import { Bold, Italic, Underline } from 'lucide-react'
import { ToggleGroup, ToggleGroupItem } from 'ui'
export function ToggleGroupDemo() {
return (
<ToggleGroup type="multiple">
<ToggleGroupItem value="bold" aria-label="Toggle bold">
<Bold className="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="italic" aria-label="Toggle italic">
<Italic className="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="underline" aria-label="Toggle underline">
<Underline className="h-4 w-4" />
</ToggleGroupItem>
</ToggleGroup>
)
}Outline
Single
Small
Large
Disabled
Segmented
A Supabase addition, not part of shadcn or Radix. A segmented control is a compact row of two to four mutually exclusive options sharing one continuous track, for switching view state (Data or Definition) or filtering a list (All, Active, Revoked). The choice takes effect immediately and is never saved, so reach for a Switch or Radio Group when the setting is persisted, and Tabs when the options swap whole panels of content. Items paint nothing themselves; a single indicator slides between them, so the group always has exactly one thing selected. Give a filter an explicit All segment rather than letting deselection stand for "no filter".
'use client'
import * as React from 'react'
import { ToggleGroup, ToggleGroupItem } from 'ui'
const SIZES = ['tiny', 'sm', 'default', 'lg'] as const
export function ToggleGroupSegmentedFilter() {
const [status, setStatus] = React.useState('all')
return (
<div className="flex flex-col gap-4">
{SIZES.map((size) => (
<div key={size} className="flex items-center gap-4">
<span className="w-16 shrink-0 text-xs text-foreground-lighter">{size}</span>
<ToggleGroup
type="single"
variant="segmented"
size={size}
value={status}
onValueChange={setStatus}
allowDeselect={false}
aria-label={`Filter keys by status (${size})`}
>
<ToggleGroupItem value="all">All</ToggleGroupItem>
<ToggleGroupItem value="active">Active</ToggleGroupItem>
<ToggleGroupItem value="revoked">Revoked</ToggleGroupItem>
</ToggleGroup>
</div>
))}
</div>
)
}Props
ToggleGroup forwards every prop to the underlying
Radix Toggle Group.
These are the ones worth knowing about.
| Prop | Type | Default | Description |
|---|---|---|---|
type | 'single' | 'multiple' | Required by Radix. Only a 'single' group gets the segmented indicator; a 'multiple' group falls back to items painting their own background. | |
variant | 'default' | 'outline' | 'segmented' | 'default' | Styles the root and every item, which inherit it through context. segmented drops the gaps and per-item borders in favor of an indicator that slides between segments, respecting prefers-reduced-motion. |
size | 'tiny' | 'sm' | 'default' | 'lg' | 'default' | Item height and padding, inherited through context. tiny suits a dense toolbar or table footer. Segments hug their labels, so add className="flex-1" to each item, plus a width on the root, if you need them equal. |
tone | 'text' | 'outline' | 'primary' | 'text' | Segmented only. text is a flat bg-accent fill, outline adds a container border and a raised bordered indicator, primary fills the indicator with brand. |
allowDeselect | boolean | true | Whether clicking the active item in a type="single" group clears it, emitting ''. Set false for a segmented control, which has no empty state. |
aria-label | string | Names what is being switched or filtered. The segments name the options, not the axis. |
variant and size are the shadcn variant axes; segmented, tone and allowDeselect are the
Supabase additions. default and outline are unchanged from shadcn.