Connects ToggleButtons into a segmented bar — single-select or multi-select.

Last updated

Uses JavaScript

Roving tabindex (one tab stop; arrows and Home/End move focus), and for type="single" a click listener on each child button that un-presses its siblings, so only one stays pressed at a time.

Without JS: Each ToggleButton stays at its initial pressed state and doesn't respond to clicks (see ToggleButton), so single-select mutual exclusivity never kicks in either.

Show code
<ToggleButtonGroup type="single" aria-label="Text alignment">
<ToggleButton iconOnly pressed aria-label="Left"><AlignLeft /></ToggleButton>
<ToggleButton iconOnly aria-label="Center"><AlignCenter /></ToggleButton>
<ToggleButton iconOnly aria-label="Right"><AlignRight /></ToggleButton>
</ToggleButtonGroup>

Add

npx @ava-ui/apogee add toggle-button-group

Pulls in ToggleButton. Ships one scoped JS island (progressive enhancement).

Usage

---
import ToggleButtonGroup from '@/components/apogee/ToggleButtonGroup/ToggleButtonGroup.astro'
import ToggleButton from '@/components/apogee/ToggleButton/ToggleButton.astro'
---
<ToggleButtonGroup type="single" aria-label="Text alignment">
  <ToggleButton iconOnly aria-label="Bold"><Bold /></ToggleButton>
  <ToggleButton iconOnly aria-label="Italic"><Italic /></ToggleButton>
</ToggleButtonGroup>

Anatomy

  • Group — a role="group" wrapper (data-type="single" | "multiple") holding the buttons in its default slot.
  • ToggleButton (×N) — each child is a plain ToggleButton; the group doesn’t render or own them directly, it just reads and (in single mode) coordinates their aria-pressed.

API

@composable — exposed to Content Editors in AVA. Any other attribute passes through to the root <div>.

Prop Type Default Description
type 'single' | 'multiple' 'multiple' single: mutually exclusive (one or none pressed). multiple: each button toggles independently.
disabled boolean false Disables every button inside (the root is a <fieldset>; native, no JS).
aria-label string — Label for the group (e.g. “Text alignment”).
class string — Extra classes on the root, merged last.

Variants

Both type values:

Show code
<ToggleButtonGroup type="single" aria-label="Alignment">
<ToggleButton iconOnly pressed aria-label="Left"><AlignLeft /></ToggleButton>
<ToggleButton iconOnly aria-label="Center"><AlignCenter /></ToggleButton>
<ToggleButton iconOnly aria-label="Right"><AlignRight /></ToggleButton>
</ToggleButtonGroup>
<ToggleButtonGroup type="multiple" aria-label="Formatting">
<ToggleButton iconOnly pressed aria-label="Bold"><Bold /></ToggleButton>
<ToggleButton iconOnly pressed aria-label="Italic"><Italic /></ToggleButton>
<ToggleButton iconOnly aria-label="Underline"><Underline /></ToggleButton>
</ToggleButtonGroup>

States

  • Single-select — clicking a button un-presses every sibling; clicking the already-pressed one clears the group (each button still self-toggles).
  • Multiple-select — each button toggles independently, no coordination.
  • No JS — buttons render at their initial pressed values and don’t respond to clicks (see ToggleButton).
  • Disabled — disabled on the group disables every button; a focused button lifts above its neighbours so its outline is never clipped.
Show code
<ToggleButtonGroup type="single" aria-label="Text alignment" disabled>
<ToggleButton iconOnly pressed aria-label="Left"><AlignLeft /></ToggleButton>
<ToggleButton iconOnly aria-label="Center"><AlignCenter /></ToggleButton>
<ToggleButton iconOnly aria-label="Right"><AlignRight /></ToggleButton>
</ToggleButtonGroup>

In a form

Each button submits its value under its name while pressed; in single mode at most one does.

Show code
<form>
<ToggleButtonGroup type="single" aria-label="Alignment">
  <ToggleButton iconOnly name="align" value="left" pressed aria-label="Left"><AlignLeft /></ToggleButton>
  <ToggleButton iconOnly name="align" value="center" aria-label="Center"><AlignCenter /></ToggleButton>
  <ToggleButton iconOnly name="align" value="right" aria-label="Right"><AlignRight /></ToggleButton>
</ToggleButtonGroup>
</form>

Examples

Mixed sizes pass straight through to each ToggleButton:

Show code
<ToggleButtonGroup type="single" aria-label="Size">
<ToggleButton iconOnly size="small" pressed aria-label="Bold"><Bold /></ToggleButton>
<ToggleButton iconOnly size="small" aria-label="Italic"><Italic /></ToggleButton>
</ToggleButtonGroup>

Events

The group dispatches apogee:toggle-group-change (detail: { value: string[] }, bubbling) after any button toggles; values come from each button’s value, falling back to its index.

Accessibility

Key Action
Tab Moves focus into the group (one stop), on the pressed button or the first.
← → Move focus between buttons, wrapping at the ends; swapped under dir="rtl".
Home End First / last button.
Enter Space Toggle the focused button.
  • In single mode clicking the pressed button clears the group (a toggle, not a radio). Use SegmentedControl for exactly-one.
  • role="group" with aria-label names the whole bar for AT users; each child keeps its own aria-pressed and aria-label.
  • Focus — the focused button shows its visible focus-visible ring.
  • No JS — the group renders (a role="group" of plain buttons); with scripts off none of them toggle, in either mode.

Tokens

Token Used for
--border-neutral / --background-page Group container border + fill
(see ToggleButton) Individual button colors

Do / Don’t

  • Do always set aria-label on the group describing what it controls (“Text alignment”, “View”).
  • Do use type="single" for mutually-exclusive choices (alignment, view mode) and type="multiple" for independent toggles (bold/italic/underline).
  • Don’t mix unrelated actions in one group — every button should belong to the same setting.
  • Don’t forget each child ToggleButton still needs its own aria-label when icon-only.

Content

  • See ToggleButton content rules — each child button’s label/aria-label follows the same guidance.