Liqui Design

Toggle Group

One glass strip holding several toggles, which flatten so the strip keeps the lens.

Loading…

Installation

npx shadcn@latest add https://liqui.design/r/toggle-group.json

This one brings Toggle with it — the two share a context, and a group with nothing to group is not a component.

Installing more than one? Register the namespace once in components.json and drop the URLs:

components.json
{
  "registries": {
    "@liqui-design": "https://liqui.design/r/{name}.json"
  }
}
npx shadcn@latest add @liqui-design/toggle-group

Usage

import { Toggle } from '@/components/ui/toggle';
import { ToggleGroup } from '@/components/ui/toggle-group';
<ToggleGroup aria-label="Text alignment" defaultValue={['left']}>
  <Toggle value="left" aria-label="Align left">
    <AlignLeftIcon />
  </Toggle>
  <Toggle value="center" aria-label="Align center">
    <AlignCenterIcon />
  </Toggle>
</ToggleGroup>

The value is an array even when only one toggle can be pressed at a time — that is Base UI's shape, and it is what makes multiple a single prop rather than a different component.

Multiple

Loading…
<ToggleGroup multiple value={style} onValueChange={setStyle}>

Notes

The strip is the glass, the toggles are not

Tabs looks almost identical and resolves this the opposite way, which is worth sitting with for a second, because the rule is the same in both places: only one surface in a control gets to refract, and it should be the one that carries the meaning.

A tab strip has exactly one selection, so it has exactly one thing that moves, and a moving lens is the best thing refraction can be. A toggle group with multiple has no such thing — two toggles can be lit at once, and there is no single travelling surface available to be the lens. So the group refracts, once, for the whole control, and a pressed toggle is accent laid over glass that is already there.

That is the same call the dialog's corner dismiss makes: a second lens inside a surface has nothing to bend but the first one.

Flattening is context, not a prop

Toggle reads whether it owns its surface from a context the group provides:

<ToggleOwnsSurface.Provider value={false}>

So a toggle behaves correctly by being in the right place, and the same import works standalone and in a group without a variant to remember. It is also why toggle-group installs toggle — the context has to be the same module object in both files.

If you want a group of glass toggles instead — separate pills, each its own lens — do not use ToggleGroup; that is a row of standalone Toggles, and it is what the toggle page shows.

Orientation

orientation="vertical" stacks the strip. data-orientation lands on the group root, not on the content wrapper the toggles actually sit in, so the wrapper reads it back off the root through group-data-* — the same pattern Button uses to reach its own content from outside.

On this page