ToggleButtonGroup
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 codeHide 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 (insinglemode) coordinates theiraria-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 codeHide 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
pressedvalues and don’t respond to clicks (seeToggleButton). - Disabled —
disabledon the group disables every button; a focused button lifts above its neighbours so its outline is never clipped.
Show codeHide 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 codeHide 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 codeHide 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
singlemode clicking the pressed button clears the group (a toggle, not a radio). Use SegmentedControl for exactly-one. role="group"witharia-labelnames the whole bar for AT users; each child keeps its ownaria-pressedandaria-label.- Focus — the focused button shows its visible
focus-visiblering. - 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-labelon the group describing what it controls (“Text alignment”, “View”). - Do use
type="single"for mutually-exclusive choices (alignment, view mode) andtype="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
ToggleButtonstill needs its ownaria-labelwhen icon-only.
Content
- See
ToggleButtoncontent rules — each child button’s label/aria-labelfollows the same guidance.