affectUI › Components › Buttons

Toggle Group

A row of toggle buttons – several on at once or one that can be cleared, labels or icons, a tray or separate chips, one tab stop with arrow keys.

Free and open source (MIT). Install: npx shadcn@latest add https://www.affectui.com/r/toggle-group.json · Import from @/components/ui/toggle-group

When to use

  • Filters where several can be on at once: Running, Needs you, Failed – type="multiple"
  • A formatting or view toolbar: bold, italic, code; visible columns; layers on a map
  • One optional choice that can also be none, like Group by Agent, Model or Day – type="single", pressing it again clears it

When not to use

  • Exactly one of a few views must always be picked (List / Board) – use the Segmented Control
  • A single on or off setting – use the Switch, or an Icon Button with pressed for one tool
  • Many options or long labels – use the Filter Menu or the Select
  • Choices saved later with a form – use Checkboxes

Usage

import { useState } from "react";
import { ToggleGroup } from "@/components/ui/toggle-group";

export function RunFilters() {
  const [status, setStatus] = useState(["running", "needs-you"]);

  return (
    <ToggleGroup
      aria-label="Show runs"
      value={status}
      onChange={setStatus}
      items={[
        { value: "running", label: "Running" },
        { value: "needs-you", label: "Needs you" },
        { value: "done", label: "Done" },
        { value: "failed", label: "Failed" },
      ]}
    />
  );
}

Props

NameTypeDefaultDescription
itemsToggleGroupItem[]{ value, label?, icon?, ariaLabel?, disabled? } – give icon-only items an ariaLabel, it's also their tooltip
type"multiple" | "single""multiple"multiple: any number can be on, the value is a list · single: at most one, pressing it again turns it off
valuestring[] | string | nullControlled value – a list with type="multiple", one value or null with type="single"
defaultValuestring[] | string | nullStarting value when uncontrolled – nothing on by default
onChange(value: string[]) => void | (value: string | null) => voidCalled with the new value; a list keeps the items' order, null when the single item was turned off
variant"tray" | "buttons""tray"A light-gray track like the Segmented Control with pressed items raised as white pills, or separate white chips with an edge that wrap onto a new line
size"sm" | "md" | "lg""md"26, 30 or 36px tall (36px+ on touch screens)
disabledbooleanfalseDisables every item
aria-labelstringNames the group – or use aria-labelledby
classNamestringExtra class on the root

Keyboard

  • Tab: Focuses the group once – the item last used, or the first one that's on
  • ← / →: Moves between the items, wrapping around (↑ ↓ too); turned around in right-to-left pages
  • Home / End: Moves to the first or last item
  • Space / Enter: Turns the focused item on or off

Accessibility

  • A role="group" of real buttons with aria-pressed – screen readers say pressed or not pressed for each
  • Arrow keys only move the focus; nothing changes until Space or Enter, so moving through a filter row is safe
  • One tab stop for the whole group (roving tabindex); disabled items are skipped
  • Icon-only items need ariaLabel – it names them and shows as the tooltip
  • The ring shows only under keyboard focus (:focus-visible), never after a click

Responsive

  • buttons wrap onto a new line when they don't fit; the tray scrolls sideways instead
  • Long labels end in … rather than pushing past the container
  • Touch screens get items at least 36px tall
  • Dark mode built in: switches when an ancestor (usually <html>) has the class dark or data-theme="dark"

Requirements

  • React 19

Related

  • Segmented Control – Switch between two to six options – a raised pill or underline that glides, stretches a little and can be dragged across, with icons, counts and badges
  • Icon Button – A square button that is only an icon – ghost, outlined or solid, two sizes, a pressed state for toggles and its label as the tooltip
  • Filter Menu – Grouped filters with removable chips, collapsible checkbox groups, Clear all and full keyboard control
  • Switch – On or off for things that apply right away – label and second line, at the start or across a settings row, a thumb that stretches as you press it
  • Checkbox – A real checkbox with a label and a second line, a some-of-them state for Select all, an error and a card row – the check draws itself