Toggle
A Base UI Toggle for immediate, reversible changes to how content is shown, and the Toggle Group that joins toggles into a segmented control or a set of filter chips.
import { Toggle } from '@fairgarden-private/design/components/Toggle'
import { ToggleGroup } from '@fairgarden-private/design/components/ToggleGroup'
Use toggles for view modes, units, formatting, play and pause, and filters. Values a form submits use Radio or Checkbox Group, and panels use Tabs. Labels are authored in title case. Selection is always a 3 px inside edge, a glyph and weight 700; any fill is extra.
Toggles and states
Toggles and states
The text toggle looks like an outline Button; pressed, it takes the inverse pair and a leading ✓. Hover takes a soft --primary3 fill (on the pastels, forest and the solid fields, where soft fills drop, the label takes the accent underline instead); a pressed toggle's fill lightens to --primary11 on hover, and pressing shows the inverse pair for a moment. The icon toggle's glyph takes its heavier weight on hover. The icon toggle is a circle whose icon swaps when pressed (icon → pressedIcon, ▷ ↔ ‖), never only a filled twin of the same glyph; its label is the visually hidden accessible name. sm is 32 px with its hit area extended to 44. Disabled draws a dotted edge; a selected item is never disabled.
'use client'
import * as React from 'react'
import { Toggle } from '@fairgarden-private/design/components/Toggle'
import styles from './states.module.css'
/**
* Text toggles (pressed shows ✓, the inverse pair and weight 700), an icon
* toggle that swaps ▷ for ‖, sizes, and a disabled toggle.
*/
export function ToggleStates() {
const [playing, setPlaying] = React.useState(false)
return (
<div className={styles.stack}>
<div className={styles.row}>
<Toggle defaultPressed>Show Trails</Toggle>
<Toggle icon="zoom_in">Magnify</Toggle>
<Toggle size="sm">Small</Toggle>
<Toggle disabled>Offline Maps</Toggle>
</div>
<div className={styles.row}>
<Toggle
iconOnly
icon="play_arrow"
pressedIcon="pause"
pressed={playing}
onPressedChange={setPlaying}
>
{playing ? 'Pause Birdsong' : 'Play Birdsong'}
</Toggle>
<span className={styles.status} aria-live="polite">
{playing ? 'Playing' : 'Paused'}
</span>
</div>
</div>
)
}
Segmented control and filter chips
Segmented control and filter chips
variant="segmented" joins 2–4 cells in one 2 px outline with --role-rule dividers, hidden beside the pressed cell; cells are equal at base and hug their labels from 768 px. More than four options belong in a Select. variant="chip" makes filter chips that wrap and never scroll: a quiet 1.5 px --role-rule edge at rest that turns --primary12 at the same weight on hover, with the soft fill where it applies, and --role-select when pressed (--role-select-hover on hover). A single-choice group marks the pressed item with ●, a multiple group with ✓. label renders a visible group label, which may echo the value ("Size: M").
'use client'
import * as React from 'react'
import { Toggle } from '@fairgarden-private/design/components/Toggle'
import { ToggleGroup } from '@fairgarden-private/design/components/ToggleGroup'
import styles from './groups.module.css'
const sizes = ['XS', 'S', 'M', 'L', 'XL']
/**
* A segmented view switch (single: ●), filter chips (multiple: ✓) and a
* single-choice chip group whose label echoes the value.
*/
export function ToggleGroups() {
const [size, setSize] = React.useState<string[]>(['M'])
return (
<div className={styles.stack}>
<ToggleGroup variant="segmented" label="View" defaultValue={['map']}>
<Toggle value="map">Map</Toggle>
<Toggle value="list">List</Toggle>
<Toggle value="grid">Grid</Toggle>
</ToggleGroup>
<ToggleGroup variant="chip" label="Filter Trails" multiple defaultValue={['shaded']}>
<Toggle value="shaded">Shaded</Toggle>
<Toggle value="loop">Loop Trail</Toggle>
<Toggle value="dogs">Dog Friendly</Toggle>
<Toggle value="water">Near Water</Toggle>
<Toggle value="access" disabled>
Step Free
</Toggle>
</ToggleGroup>
<ToggleGroup
variant="chip"
label={`Size: ${size[0] ?? 'None'}`}
value={size}
onValueChange={setSize}
>
{sizes.map((value) => (
<Toggle key={value} value={value}>
{value}
</Toggle>
))}
</ToggleGroup>
<ToggleGroup label="Units" defaultValue={['metric']}>
<Toggle value="metric">Metric</Toggle>
<Toggle value="imperial">Imperial</Toggle>
</ToggleGroup>
</div>
)
}
Primary and secondary
Primary and secondary
Toggles and segments draw every part from primary. secondary drives only the selected filter chip's fill, mark and edge. Neither is defaulted.
import { Toggle } from '@fairgarden-private/design/components/Toggle'
import { ToggleGroup } from '@fairgarden-private/design/components/ToggleGroup'
import styles from './color.module.css'
/**
* Toggles and segments draw every part from `primary`; `secondary` drives
* only the selected filter chip.
*/
export function ToggleColor() {
return (
<div className={styles.grid}>
<Toggle defaultPressed primary="plum">
Primary Plum
</Toggle>
<ToggleGroup variant="chip" multiple aria-label="Scope chips" defaultValue={['a']}>
<Toggle value="a">Scope Chip</Toggle>
</ToggleGroup>
<ToggleGroup
variant="chip"
multiple
secondary="indigo"
aria-label="Indigo chips"
defaultValue={['a']}
>
<Toggle value="a">Secondary Indigo</Toggle>
</ToggleGroup>
</div>
)
}
On grounds
On paper and forest
Toggles and segments select by the inverse pair on every ground. Selected chips are green on paper and the inverse pair on forest and the saturated grounds. Toggles don't print; print the result as a caption ("View: Week").
import { Toggle } from '@fairgarden-private/design/components/Toggle'
import { ToggleGroup } from '@fairgarden-private/design/components/ToggleGroup'
import { PresetGround } from '@/components/PresetGround'
import styles from './grounds.module.css'
const presets = ['paper', 'forest'] as const
/**
* Segments take the inverse pair on every ground; the selected chip is
* green on paper and the inverse pair on forest.
*/
export function ToggleGrounds() {
return (
<div className={styles.row}>
{presets.map((preset) => (
<PresetGround key={preset} preset={preset} className={styles.face}>
<span className={styles.name}>{preset}</span>
<ToggleGroup variant="segmented" aria-label={`View on ${preset}`} defaultValue={['day']}>
<Toggle value="day">Day</Toggle>
<Toggle value="week">Week</Toggle>
</ToggleGroup>
<ToggleGroup
variant="chip"
multiple
aria-label={`Filters on ${preset}`}
defaultValue={['birds']}
>
<Toggle value="birds">Birds</Toggle>
<Toggle value="ferns">Ferns</Toggle>
</ToggleGroup>
</PresetGround>
))}
</div>
)
}
API Reference
Toggle
A Base UI Toggle: an on/off button for immediate, reversible changes to how content is shown (view, units, formatting, filters). Selection is a heavier inside edge, a glyph and a weight change; fill is extra [D15]. Values a form submits use Radio or Checkbox Group; panels use Tabs.
| Prop | Type | Description |
|---|---|---|
icon | | One optional leading glyph (§6.10), inline tier, FILL 0. |
iconOnly | |
|
pressedIcon | | Not used on a labelled toggle: it shows ✓ or ● when pressed. |
primary | | Primary Radix scale: edges, label, the inverse pair and the ring. Never defaulted [D133]. |
secondary | | Secondary Radix scale: drives only the selected chip (--role-select). Never defaulted. |
size | | Height: |
variant | |
|
children | | The label, authored in title case (“Grid View”) [D160]. |
ToggleGroup
A Base UI Toggle Group of Toggles, single (default) or multiple. The
group passes segment or chip to its Toggles and tells them whether
the pressed mark is ● (single) or ✓ (multiple). Use it for immediate,
reversible view changes and filters, never to switch panels (Tabs).
The outline and chip roots are inline-size containers, so give them a
width in shrink-to-fit layouts; the segmented root sizes to its cells.
| Prop | Type | Description |
|---|---|---|
label | | A visible group label ( |
primary | | Primary Radix scale for the group and its toggles. Never defaulted [D133]. |
secondary | | Secondary Radix scale: the selected chips’ --role-select. Never defaulted. |
variant | |
|
Additional types
toggleGroup
type toggleGroup = toggleGroupToggleItemContext
Provided by ToggleGroup; a standalone Toggle reads undefined.
type ToggleItemContext = React.Context<ToggleItemContextValue | undefined>ToggleItemContextValue
What a ToggleGroup tells the toggles inside it.
type ToggleItemContextValue = {
/** The item look the group's variant implies. */
variant: ToggleVariant;
/** Whether the group allows several pressed items: ✓ rather than ●. */
multiple: boolean;
}ToggleProps
Props for Toggle: Base UI Toggle props plus the variant, size and color
axes. With iconOnly, icon, pressedIcon and an accessible-name
children are required.
type ToggleProps = (
| {
iconOnly?: false | null;
icon?:
| 'menu'
| 'search'
| 'circle'
| 'arrow_forward'
| 'arrow_upward'
| 'expand_more'
| 'close'
| 'remove'
| 'add'
| 'check'
| 'chevron_right'
| 'chevron_left'
| 'more_horiz'
| 'play_arrow'
| 'pause'
| 'download'
| 'zoom_in'
| 'zoom_out'
| 'recenter'
| 'help'
| 'mail';
pressedIcon?: undefined;
children?: React.ReactNode;
}
| {
iconOnly: true;
icon:
| 'menu'
| 'search'
| 'circle'
| 'arrow_forward'
| 'arrow_upward'
| 'expand_more'
| 'close'
| 'remove'
| 'add'
| 'check'
| 'chevron_right'
| 'chevron_left'
| 'more_horiz'
| 'play_arrow'
| 'pause'
| 'download'
| 'zoom_in'
| 'zoom_out'
| 'recenter'
| 'help'
| 'mail';
pressedIcon:
| 'menu'
| 'search'
| 'circle'
| 'arrow_forward'
| 'arrow_upward'
| 'expand_more'
| 'close'
| 'remove'
| 'add'
| 'check'
| 'chevron_right'
| 'chevron_left'
| 'more_horiz'
| 'play_arrow'
| 'pause'
| 'download'
| 'zoom_in'
| 'zoom_out'
| 'recenter'
| 'help'
| 'mail';
children: React.ReactNode;
}
) & {
/**
* `outline` (default): a pill like the outline Button, selected by the
* inverse pair. `segment`: a joined cell of a segmented group. `chip`: a
* filter chip, selected by --role-select. Inside a ToggleGroup the group
* sets it.
*/
variant?: 'outline' | 'segment' | 'chip' | null;
/** Primary Radix scale: edges, label, the inverse pair and the ring. Never defaulted [D133]. */
primary?:
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| null;
/** Secondary Radix scale: drives only the selected chip (--role-select). Never defaulted. */
secondary?:
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| 'amber'
| 'blue'
| 'cyan'
| 'grass'
| 'green'
| 'jade'
| 'lime'
| 'mint'
| 'orange'
| 'sky'
| 'teal'
| 'yellow'
| null;
/**
* Height: `sm` 32 px (chips, toolbars; hit area extended to 44) or `md`
* 40 px (segments, standalone; default, chips default to `sm`).
*/
size?: 'sm' | 'md' | null;
}ToggleVariant
A Toggle’s look (§9.4).
type ToggleVariant = 'outline' | 'segment' | 'chip'<'outline' | 'segment' | 'chip' | null | undefined>Specification: DESIGN-SYSTEM.md §9.4 (toggle and toggle group, filter chips and segments).