Toggle Group

A set of two-state buttons that can be toggled on or off.

This component uses Radix UI
This component uses shadcn/uiDocs
Loading...
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-group

Usage

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

Loading...
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

Loading...

Single

Loading...

Small

Loading...

Large

Loading...

Disabled

Loading...

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".

Loading...
Loading...
'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.

PropTypeDefaultDescription
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.
allowDeselectbooleantrueWhether clicking the active item in a type="single" group clears it, emitting ''. Set false for a segmented control, which has no empty state.
aria-labelstringNames 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.