apx-dsv0.1Local renderer · live
K
Foundations4
  • Getting started
  • Theming
  • Templates
  • Icons
  • Avatar
  • Badge
  • DataGrid
  • Scheduler
  • Stat
  • Table
  • Timeline
  • TreeView
  • Accordion
  • Alert
  • EmptyState
  • Progress
  • Skeleton
  • Spinner
  • SplashScreen
  • Toast
  • ColorPicker
  • FileUpload
  • Form
  • Rating
  • TagsInput
  • Combobox
  • Field
  • Select
  • Toggle
  • Button
  • Calendar
  • Checkbox
  • DatePicker
  • Input
  • NumberInput
  • Radio
  • Slider
  • Switch
  • Textarea
  • AppShell
  • Div
  • Divider
  • Sidebar
  • Stack
  • Typography
  • Image
  • Breadcrumbs
  • Carousel
  • NavigationMenu
  • Pagination
  • Stepper
  • Tabs
  • Toolbar
  • CommandPalette
  • Confirm
  • Drawer
  • HoverCard
  • Menu
  • Modal
  • Popover
  • Tooltip
  • Icon
  • Card
  • PricingCard
60 componentsapx-ds/renderer
Toggle
Variant↳ other

Forms

Toggle

Binary-state button + segmented multi-button group. Standalone <Toggle> for one-off pressed-state affordances; <ToggleGroup type=

Overview

<Toggle> and <ToggleGroup> are the design system's press-state primitives — buttons that remember whether they're currently "on". <Toggle> is the one-off case (a sidebar collapse, a "show/hide" affordance, a mute button); <ToggleGroup> is the coordinated case (segmented controls, single-pick toolbars, multi-pick formatting bars).

They share a visual language with <Button> on purpose — a pressed toggle should look like a button that's been activated, not a different control altogether.

Overview — text-only, icon-only, labeled, and grouped toggles

Loading preview…
Overview.tsx

Why this exists

Two patterns recur across product UIs and deserve a first-class primitive instead of being re-invented per app:

  1. Pressed state without a checkbox. Toolbars and segmented controls aren't form fields in the HTML sense — they don't post values, they don't live inside <form>s. Using <input type="checkbox"> styled to look like a button just to get press semantics buys you accessibility headaches (form-control labelling rules, browser default focus rings, tab-order surprises). <Toggle> / <ToggleGroup.Item> is role="button" / role="radio" plus aria-pressed / aria-checked — exactly the right ARIA for the visual.
  2. Single-pick vs multi-pick on one component. Toolbars need both ("alignment is a single-pick; bold/italic/underline are multi-pick"). <ToggleGroup type="..."> is the discriminator — same recipe, same keyboard model, same theming, two ARIA contracts.

Modes

<ToggleGroup> is a discriminated union on type:

  • type="single" (default) — value is a string. At most one item pressed. Root is role="radiogroup", items are role="radio", arrow keys move focus and activate (the canonical radio pattern). Set required={true} to forbid the user from clearing the active item (one value is always pressed).
  • type="multiple" — value is string[]. Each item toggles independently. Root is role="group", items are role="button" with aria-pressed, arrow keys move focus without activating; Space / Enter toggle.

Anatomy

tsx
┌────────────────────────────────────────────────────────────┐
│ ToggleGroup (role=radiogroup or role=group)               │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐                    │
│ │  Item    │ │  Item    │ │  Item    │   …                │
│ │  (radio  │ │  (radio  │ │  (radio  │                    │
│ │  or btn) │ │  or btn) │ │  or btn) │                    │
│ └──────────┘ └──────────┘ └──────────┘                    │
└────────────────────────────────────────────────────────────┘

┌────────────────┐
│  Toggle        │   role="button" + aria-pressed
│  (standalone)  │
└────────────────┘
┌────────────────────────────────────────────────────────────┐
│ ToggleGroup (role=radiogroup or role=group)               │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐                    │
│ │  Item    │ │  Item    │ │  Item    │   …                │
│ │  (radio  │ │  (radio  │ │  (radio  │                    │
│ │  or btn) │ │  or btn) │ │  or btn) │                    │
│ └──────────┘ └──────────┘ └──────────┘                    │
└────────────────────────────────────────────────────────────┘

┌────────────────┐
│  Toggle        │   role="button" + aria-pressed
│  (standalone)  │
└────────────────┘

<Toggle> is intentionally standalone — it does not require (and does not consume) a <ToggleGroup> ancestor. Reach for it when you have one toggle, not a coordinated set.

Variants × Colors

Three variants (solid / outline / ghost) × seven colors. The variant determines the off-state chrome; the data-state="on" rules layer the pressed-state tint on top using the color role:

VariantOff statePressed state (color-tinted)
solidsubtle tinted surface (color-subtle bg)bold tinted surface (color-solid bg + inverted fg)
outlinebordered, transparent bgfilled (color-solid bg + inverted fg)
ghosttransparentfilled tint (color-subtle bg + color-solid fg)

Sizes

SizeHeightPadding (icon-only)Padding (with label)FontIcon
sm32pxmin-w-8px-2text-xs16px
md40pxmin-w-10px-3text-sm16px
lg48pxmin-w-12px-4text-base16px

Attached (segmented control)

Set attached={true} on <ToggleGroup> to merge neighboring items into a single bar with shared borders and flattened inner corners — the classic "segmented control" look. Rounding adapts to the group's orientation, and uses logical properties (rounded-s-md, rounded-e-md, ms-, me-) so RTL works for free.

Examples

Standalone toggle — pressed state

Loading preview…
BasicToggle.tsx

ToggleGroup type=single (radio semantics)

Loading preview…
BasicGroupSingle.tsx

ToggleGroup type=multiple (independent press)

Loading preview…
BasicGroupMultiple.tsx

Attached — segmented control

Loading preview…
Attached.tsx

Vertical orientation (stacked + attached)

Loading preview…
Vertical.tsx

Variants (solid / outline / ghost)

Loading preview…
Variants.tsx

Three sizes (sm / md / lg)

Loading preview…
Sizes.tsx

Seven colors × solid variant

Loading preview…
Colors.tsx

type=single + required (cannot deselect)

Loading preview…
Required.tsx

Icon + text label

Loading preview…
WithLabel.tsx

Icon-only items (with aria-label)

Loading preview…
IconOnly.tsx

Per-item disable + group disable

Loading preview…
DisabledItem.tsx

Composed toolbar — multi + single in one bar

Loading preview…
Toolbar.tsx

Props

Toggle (standalone)

PropTypeDefaultNotes
pressedboolean—Controlled pressed state. Pair with onPressedChange.
defaultPressedbooleanfalseUncontrolled initial state.
onPressedChange(pressed: boolean) => void—Fires on every flip.
variant'solid' | 'outline' | 'ghost''ghost'Stylistic family. Responsive via { base, md } shape.
size'sm' | 'md' | 'lg''md'
colorone of seven palette roles'neutral'Drives the pressed-state tint.
disabledbooleanfalse
aria-labelstring—Required when children are icon-only.
classNamestring—Merged via tailwind-merge.
sxSx—Theme-aware inline style.

ToggleGroup (root)

PropTypeDefaultNotes
type'single' | 'multiple''single'Discriminates value / defaultValue / onValueChange between string/array.
valuestring | string[]—Controlled. Pair with onValueChange.
defaultValuestring | string[]'' / []Uncontrolled initial.
onValueChange(value: string | string[]) => void—
requiredbooleanfalseSingle-mode only. Clicking the active item is a no-op (always pressed).
variant'solid' | 'outline' | 'ghost''ghost'
size'sm' | 'md' | 'lg''md'
colorone of seven palette roles'neutral'Pressed-state tint for every item.
attachedbooleanfalseMerge items into a segmented control.
orientation'horizontal' | 'vertical''horizontal'Layout axis; also remaps arrow keys.
disabledbooleanfalseDisables every item. Per-item disabled is additive.
aria-labelstring—One of aria-label / aria-labelledby is required.
aria-labelledbystring—

ToggleGroup.Item

PropTypeDefaultNotes
valuestringrequiredUnique key identifying this item in the group's value.
disabledbooleanfalseDisable just this item.
aria-labelstring—Required when children are icon-only.

Accessibility

  • W3C ARIA patterns. type="single" follows the Radio Group pattern (role="radiogroup" + role="radio" + aria-checked + roving tabindex + arrow-activate). type="multiple" follows the Toolbar / press-button pattern (role="group" + role="button" + aria-pressed + all items tabbable + arrow-move-focus-only).
  • Keyboard. Arrow keys move (and in single mode, activate). Home/End jump to the first / last enabled item. Space and Enter activate via native <button> semantics. Disabled items are skipped during navigation.
  • Roving tabindex (single mode). Only the pressed item is in the tab sequence; if nothing is pressed, the first enabled item is the entry point. Multi-mode keeps every item in the tab sequence — there's no "active" item to rove on, so the user can Tab through marks individually.
  • Group labels. A <ToggleGroup> must carry an aria-label or aria-labelledby. Dev builds emit a console warning otherwise.
  • Icon-only items. An item whose children are only an icon must supply its own aria-label. Dev builds emit a warning when this is missing.
  • Reduced motion. The pressed-state transition shortens to ~120ms under prefers-reduced-motion: reduce. There are no JS-driven animations to disable.
  • axe-core. Zero violations across the full 3 × 7 variant × color matrix, in both modes, vertical and horizontal orientations, attached and non-attached.

Theming

ts
defineTheme({
  components: {
    Toggle: {
      defaultProps: { variant: 'outline', size: 'sm', color: 'primary' },
      styleOverrides: {
        root: '',
      },
    },
    ToggleGroup: {
      defaultProps: { variant: 'outline', size: 'sm' },
      styleOverrides: {
        root: '',
        item: 'data-[state=on]:shadow-inner',
      },
    },
  },
});
defineTheme({
  components: {
    Toggle: {
      defaultProps: { variant: 'outline', size: 'sm', color: 'primary' },
      styleOverrides: {
        root: '',
      },
    },
    ToggleGroup: {
      defaultProps: { variant: 'outline', size: 'sm' },
      styleOverrides: {
        root: '',
        item: 'data-[state=on]:shadow-inner',
      },
    },
  },
});

Slot keys: root (and item on ToggleGroup for the attached-position styling).

Props

PropTypeDefaultDescription
childrenReactNode—Children — typically an icon or short label. Icon-only toggles MUST set `aria-label`.
colorResponsiveValue<ToggleColor>'neutral'Palette role driving the pressed-state tint.
defaultPressedbooleanfalseUncontrolled initial pressed state.
onPressedChange((pressed: boolean) => void)—Fires when the user toggles the button (controlled + uncontrolled).
pressedboolean—Controlled pressed state. Pair with `onPressedChange`.
sizeResponsiveValue<ToggleSize>'md'Size axis (height + padding + icon).
sxSx—Theme-aware inline style object.
variantResponsiveValue<ToggleVariant>'ghost'Stylistic family.