Gesso
Actions

Toggle group

Exclusive views in a stroked well, or a cluster of flags.

Toggle group is a row of toggles that works as one control, for picking the view out of several, like List or Board, or for a set of flags that each stay on or off. By default the items sit in a sunken well and the one that is on is raised, and density="dots" shrinks them to dots that open into icons on hover. It changes the view you are already in and owns no panels, which is what sets it apart from Tabs.

Usage

import { List, SquaresFour } from '@phosphor-icons/react';
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';

<ToggleGroup type="single" value={view} onValueChange={setView}>
  <ToggleGroupItem value="list">
    <List />
    List
  </ToggleGroupItem>
  <ToggleGroupItem value="board">
    <SquaresFour />
    Board
  </ToggleGroupItem>
</ToggleGroup>

The well

The default variant="well" is a sunken track on bg-surface-sunken with a stroke, and it follows the look's control corner, 4px rounder than the items inside it, so it is a pill where buttons are pills, as in ICS, and nearly square in Vistula. The item that is on rises onto bg-surface-raised with shadow-sm with its label in text-fg and its icon in text-primary, and the others stay text-fg-muted until hovered. The well is 40px tall and sizes its own items, so it ignores size and spacing, and an icon inside is 16px.

One or several

type="single" keeps exactly one item on. Pressing the item that is already on does nothing, so the group never falls back to empty and onValueChange only ever receives a value. Give it a value or a defaultValue, so one item is on from the start.

type="multiple" takes an array of values in the same well. Each item turns on and off by itself, so any number of them can be raised, including none.

Dots for a row of icons

density="dots" shrinks each item in the well to an 8px dot, with the one that is on in bg-primary. While the pointer is over the group or focus is inside it, the dots open into 28px icon buttons, with the selected icon in text-primary. A touch screen has no hover, so there the group stays open with 44px items. Put only an icon in each item, with an aria-label, and the label names the item and shows as a tooltip below it.

<ToggleGroup type="single" density="dots" value={view} onValueChange={setView}>
  <ToggleGroupItem value="list" aria-label="List">
    <List />
  </ToggleGroupItem>
  <ToggleGroupItem value="board" aria-label="Board">
    <SquaresFour />
  </ToggleGroupItem>
</ToggleGroup>

Joined or spaced

With variant="default" or variant="outline", each item is drawn as a Toggle instead, with its on state in bg-fill and its height from size. At the default spacing={0} the items join into one control with square inner corners, and outline items share the border between them. Set spacing to a number of pixels to pull them apart into separate toggles.

Props

Toggle group takes every prop of the Radix Toggle group, such as type, value, defaultValue, onValueChange, disabled, and orientation, plus these. ToggleGroupItem needs a value and takes no variant or size of its own, so set those on the group.

PropTypeDefaultWhat it does
variant"well" | "default" | "outline""well"well is the sunken track with a raised item. default and outline draw each item as a Toggle.
density"default" | "dots""default"With the well, dots shrinks the items to dots that open into icons on hover and focus.
size"sm" | "default" | "lg""default"32, 36, or 40px tall for default and outline items. The well ignores it.
spacingnumber0The gap between default and outline items, in pixels. At 0 they join. The well ignores it.

type="single" works as on Radix, except that it cannot be cleared, so onValueChange never receives an empty string.

Accessibility

  • With type="single" the group is a radiogroup and each item is a radio with aria-checked. With type="multiple" the group is a toolbar and each item is a toggle button with aria-pressed.
  • The group is one tab stop, and Tab lands on the item that is on. The arrow keys move between items and wrap at the ends, and Home and End jump to the first and last. Arrows only move focus, so Space or Enter is what turns an item on. Disabled items are skipped.
  • An item's name is its text. An item with only an icon needs an aria-label, and in dots that label is also its tooltip.
  • Focus shows a 2px ring on :focus-visible only, in the contrast ink, and the focused item rises above its neighbors so a joined row does not cover the ring.
  • On a touch screen each well item gets a 44px hit area around it without changing its size, dots open to 44px items, and default and outline items are at least 44px tall.
  • Under reduced motion the raised item, the dots, and the icons change without animating, and the tooltip appears without a fade.
  • In forced colors the item that is on has no visible mark, because the system replaces its raised fill and drops its shadow, and the collapsed dots are fills too, so they do not show until the group opens. Assistive tech still reports the state from aria-checked or aria-pressed.

When to use something else

  • Tabs when each option owns a panel of content. Tabs gives its triggers and panels the tab roles and marks the active tab with a line on the list's edge, and Toggle group only reports a value.
  • Tab switcher builds this same well, with dots for icons, from a tabs array. Use Toggle group directly in new work.
  • Radio group for a choice that a form submits, with a label beside each option.
  • Toggle for a single state on its own.
  • Button group for actions joined into one control, where nothing stays on.

Install

shadcn add @gesso/toggle-group

On this page