PixelToggleGroup

Grouped pressable toggles with single- or multi-select semantics, roving focus, and surface-aware styling.

  • Discriminated union API: type="single" → string value, type="multiple" → string[] value
  • Optional roving tabindex with arrow-key navigation and Home/End support
  • Single mode exposes radiogroup/radio semantics; multi mode uses aria-pressed buttons
  • Surface-aware (pixel/linear) and supports soft, solid, outline, and ghost variants
Status
stable
Category
forms
Since
v1.9.0

API

import { PixelToggleGroup } from '@pxlkit/ui-kit';
PropTypeDefaultDescription
type'single' | 'multiple''single'single: one value, a radiogroup of radios; multiple: any number of values, pressed buttons.
valuestring | string[]—Value: a string in single mode ('' for none), an array in multiple mode; leave unset for an uncontrolled group.
defaultValuestring | string[]—Initial value while uncontrolled; nothing pressed by default.
onChange((next: string) => void) | ((next: string[]) => void)—Called with the new value, after each press.
rovingFocusbooleanfalseOnly one toggle is in the tab order; the arrow keys, Home and End move between them.
loopbooleanfalseThe arrow keys wrap from the last toggle to the first and back.
size'sm' | 'md' | 'lg''md'Size of the toggles.
variant'solid' | 'soft' | 'ghost' | 'outline''soft'Variant of the toggles.
surface'pixel' | 'linear'—Surface of the toggles; defaults to the nearest provider.
aria-labelstring—Accessible label for the toolbar / radiogroup.
children (required)React.ReactNode—The PixelToggles.

Also takes the native attributes and event handlers of <div> (HTMLAttributes<HTMLDivElement>).

ref points to <div>.

Accessibility

WCAG target: 2.1 AA

  • radiogroup
  • toolbar

Provide an aria-label (or aria-labelledby) for screen reader context. Single-select renders role="radiogroup" with role="radio" children; multi-select renders role="group" only when named.

Keyboard

KeyActionWhen
ArrowRight / ArrowDownMove focus to next itemrovingFocus
ArrowLeft / ArrowUpMove focus to previous itemrovingFocus
HomeMove focus to first itemrovingFocus
EndMove focus to last itemrovingFocus
Space / EnterToggle the focused item—

Usage

import { useState } from 'react';
import { PixelToggle, PixelToggleGroup } from '@pxlkit/ui-kit';

export function Default() {
  const [value, setValue] = useState<string>('left');
  return (
    <PixelToggleGroup
      type="single"
      value={value}
      onChange={setValue}
      aria-label="Text alignment"
    >
      <PixelToggle value="left">Left</PixelToggle>
      <PixelToggle value="center">Center</PixelToggle>
      <PixelToggle value="right">Right</PixelToggle>
    </PixelToggleGroup>
  );
}

Examples

Default

import { useState } from 'react';
import { PixelToggle, PixelToggleGroup } from '@pxlkit/ui-kit';

export function Default() {
  const [value, setValue] = useState<string>('left');
  return (
    <PixelToggleGroup
      type="single"
      value={value}
      onChange={setValue}
      aria-label="Text alignment"
    >
      <PixelToggle value="left">Left</PixelToggle>
      <PixelToggle value="center">Center</PixelToggle>
      <PixelToggle value="right">Right</PixelToggle>
    </PixelToggleGroup>
  );
}

Multiple

import { useState } from 'react';
import { PixelToggle, PixelToggleGroup } from '@pxlkit/ui-kit';

export function Multiple() {
  const [value, setValue] = useState<string[]>(['bold']);
  return (
    <PixelToggleGroup
      type="multiple"
      value={value}
      onChange={setValue}
      aria-label="Text formatting"
    >
      <PixelToggle value="bold">Bold</PixelToggle>
      <PixelToggle value="italic">Italic</PixelToggle>
      <PixelToggle value="underline">Underline</PixelToggle>
    </PixelToggleGroup>
  );
}

Variants

import { useState } from 'react';
import { PixelToggle, PixelToggleGroup } from '@pxlkit/ui-kit';

export function Variants() {
  const [a, setA] = useState<string>('one');
  const [b, setB] = useState<string>('one');
  const [c, setC] = useState<string>('one');
  const [d, setD] = useState<string>('one');
  return (
    <div className="space-y-3">
      <PixelToggleGroup type="single" variant="soft" value={a} onChange={setA} aria-label="Soft">
        <PixelToggle value="one">One</PixelToggle>
        <PixelToggle value="two">Two</PixelToggle>
        <PixelToggle value="three">Three</PixelToggle>
      </PixelToggleGroup>
      <PixelToggleGroup type="single" variant="solid" value={b} onChange={setB} aria-label="Solid">
        <PixelToggle value="one">One</PixelToggle>
        <PixelToggle value="two">Two</PixelToggle>
        <PixelToggle value="three">Three</PixelToggle>
      </PixelToggleGroup>
      <PixelToggleGroup type="single" variant="outline" value={c} onChange={setC} aria-label="Outline">
        <PixelToggle value="one">One</PixelToggle>
        <PixelToggle value="two">Two</PixelToggle>
        <PixelToggle value="three">Three</PixelToggle>
      </PixelToggleGroup>
      <PixelToggleGroup type="single" variant="ghost" value={d} onChange={setD} aria-label="Ghost">
        <PixelToggle value="one">One</PixelToggle>
        <PixelToggle value="two">Two</PixelToggle>
        <PixelToggle value="three">Three</PixelToggle>
      </PixelToggleGroup>
    </div>
  );
}

Sizes

import { useState } from 'react';
import { PixelToggle, PixelToggleGroup } from '@pxlkit/ui-kit';

export function Sizes() {
  const [sm, setSm] = useState<string>('a');
  const [md, setMd] = useState<string>('a');
  const [lg, setLg] = useState<string>('a');
  return (
    <div className="space-y-3">
      <PixelToggleGroup type="single" size="sm" value={sm} onChange={setSm} aria-label="Small">
        <PixelToggle value="a">A</PixelToggle>
        <PixelToggle value="b">B</PixelToggle>
        <PixelToggle value="c">C</PixelToggle>
      </PixelToggleGroup>
      <PixelToggleGroup type="single" size="md" value={md} onChange={setMd} aria-label="Medium">
        <PixelToggle value="a">A</PixelToggle>
        <PixelToggle value="b">B</PixelToggle>
        <PixelToggle value="c">C</PixelToggle>
      </PixelToggleGroup>
      <PixelToggleGroup type="single" size="lg" value={lg} onChange={setLg} aria-label="Large">
        <PixelToggle value="a">A</PixelToggle>
        <PixelToggle value="b">B</PixelToggle>
        <PixelToggle value="c">C</PixelToggle>
      </PixelToggleGroup>
    </div>
  );
}

Roving focus

import { useState } from 'react';
import { PixelToggle, PixelToggleGroup } from '@pxlkit/ui-kit';

export function RovingFocus() {
  const [value, setValue] = useState<string>('list');
  return (
    <PixelToggleGroup
      type="single"
      rovingFocus
      loop
      value={value}
      onChange={setValue}
      aria-label="View mode"
    >
      <PixelToggle value="list">List</PixelToggle>
      <PixelToggle value="grid">Grid</PixelToggle>
      <PixelToggle value="board">Board</PixelToggle>
    </PixelToggleGroup>
  );
}

Surfaces

import { useState } from 'react';
import { PixelToggle, PixelToggleGroup } from '@pxlkit/ui-kit';

export function Surfaces() {
  const [pixel, setPixel] = useState<string>('one');
  const [linear, setLinear] = useState<string>('one');
  return (
    <div className="space-y-3">
      <PixelToggleGroup
        type="single"
        surface="pixel"
        value={pixel}
        onChange={setPixel}
        aria-label="Pixel surface"
      >
        <PixelToggle value="one">One</PixelToggle>
        <PixelToggle value="two">Two</PixelToggle>
      </PixelToggleGroup>
      <PixelToggleGroup
        type="single"
        surface="linear"
        value={linear}
        onChange={setLinear}
        aria-label="Linear surface"
      >
        <PixelToggle value="one">One</PixelToggle>
        <PixelToggle value="two">Two</PixelToggle>
      </PixelToggleGroup>
    </div>
  );
}

Related