Toggle group
hc-toggle-group is a connected row of toggle buttons, the shadcn
ToggleGroup equivalent. It comes in two selection modes and ships a
small behavior (installToggleGroup) for the keyboard and state logic;
the CSS is the segmented-control skin.
The mode is set with data-type on the group and reflected by the ARIA
roles on the buttons — choose the markup that matches the semantics.
Single (exclusive)
Section titled “Single (exclusive)”Only one button can be on at a time. Per the
WAI-ARIA APG, an
exclusive set of toggles is a radio group even when it looks like
buttons: use role="radiogroup" on the group and role="radio" +
aria-checked on each button. Selection follows focus (arrow keys move
and select), and the group can never be emptied by a click.
<div class="hc-toggle-group" role="radiogroup" data-type="single" aria-label="Text alignment"> <button type="button" class="hc-toggle" role="radio" aria-checked="true" data-value="left">Left</button> <button type="button" class="hc-toggle" role="radio" aria-checked="false" data-value="center">Center</button> <button type="button" class="hc-toggle" role="radio" aria-checked="false" data-value="right">Right</button></div>Multiple
Section titled “Multiple”Each button toggles independently. Use role="group" on the group and
aria-pressed on each button. Arrow keys move focus; Space / Enter /
click toggle the focused button on and off.
<div class="hc-toggle-group" role="group" data-type="multiple" aria-label="Text formatting"> <button type="button" class="hc-toggle" aria-pressed="false" data-value="bold">B</button> <button type="button" class="hc-toggle" aria-pressed="true" data-value="italic">I</button> <button type="button" class="hc-toggle" aria-pressed="false" data-value="underline">U</button></div>data-size accepts sm, md (default), and lg on the group, drawn
from the shared --hc-control-* scale (so data-density shrinks them
consistently).
Keyboard
Section titled “Keyboard”| Key | Single (radio) | Multiple (group) |
|---|---|---|
Tab | Enters / leaves the group (one stop). | Same. |
→ / ↓ | Move focus to next + select it. | Move focus to next. |
← / ↑ | Move focus to previous + select it. | Move focus to previous. |
Home / End | First / last enabled + select. | First / last enabled. |
Space / Enter | Select the focused button. | Toggle the focused button. |
Navigation wraps around the ends and skips disabled buttons
(disabled or aria-disabled="true"). The group is a single
Tab stop via a roving tabindex.
JavaScript
Section titled “JavaScript”import { installToggleGroup } from '@hypermedia-components/core';installToggleGroup(); // idempotent; returns an uninstallerThe zero-config @hypermedia-components/core/behaviors entry installs
it automatically. Every change dispatches a bubbling
hc:togglegroupchange on the group:
group.addEventListener('hc:togglegroupchange', (e) => { // single → { type:'single', value, item, group } // multiple → { type:'multiple', values, item, pressed, group } console.log(e.detail);});value / values come from each button’s data-value attribute.
Form integration
Section titled “Form integration”Set data-name="X" on the group and the behavior maintains hidden
inputs so it serialises like a native control — one <input type="hidden" name="X"> for the checked value (single), or one per pressed value
(multiple). No JS wiring needed on the server side.
<div class="hc-toggle-group" role="radiogroup" data-type="single" data-name="view" aria-label="View"> <button type="button" class="hc-toggle" role="radio" aria-checked="true" data-value="grid">Grid</button> <button type="button" class="hc-toggle" role="radio" aria-checked="false" data-value="list">List</button></div><!-- submits view=grid -->htmx usage
Section titled “htmx usage”Drive a request straight off the change event — e.g. a segmented view switcher that swaps a region:
<div class="hc-toggle-group" role="radiogroup" data-type="single" aria-label="View" data-hx-get="/reports" data-hx-trigger="hc:togglegroupchange" data-hx-include="this" data-hx-target="#report" data-name="range"> <button type="button" class="hc-toggle" role="radio" aria-checked="true" data-value="7d">7d</button> <button type="button" class="hc-toggle" role="radio" aria-checked="false" data-value="30d">30d</button> <button type="button" class="hc-toggle" role="radio" aria-checked="false" data-value="90d">90d</button></div>data-hx-include="this" picks up the hidden input the data-name
integration writes, so the request carries range=7d etc.
Hyperscript
Section titled “Hyperscript”Reflect the selection inline (the detail carries value for single,
values for multiple):
<div class="hc-toggle-group" role="radiogroup" data-type="single" aria-label="View" _="on hc:togglegroupchange add .is-{event.detail.value} to #grid"> …</div>More patterns: Hyperscript → Reacting to component events.
Accessibility
Section titled “Accessibility”- Give the group an accessible name with
aria-label(oraria-labelledby). - Pick the role that matches the semantics: an exclusive choice is a
radio group, not a set of independent toggles. Using
aria-pressedfor an exclusive choice misleads screen-reader users into thinking several can be on at once. - The behavior keeps
aria-checked/aria-pressedand the roving tabindex in sync; you only set the initial state in markup. - Disabled buttons are skipped by keyboard navigation.
Theming tokens
Section titled “Theming tokens”Component tokens (in component.tokens.json):
| Token path | Purpose |
|---|---|
toggle.height / padding-x / radius / font-size / font-weight | Box metrics (height + padding-x are density-aware). |
toggle.fg / bg / border | Resting colors. |
toggle.hover-bg / hover-fg | Hover state. |
toggle.on-bg / on-fg / on-border | Selected / pressed state — the accent tracks data-color. |
toggle.disabled-fg / disabled-bg | Disabled state. |
toggle.sm.* / lg.* | Size variants. |
CSS variables
Section titled “CSS variables”Show the generated CSS variables
--hc-toggle-height | -padding-x | -radius | -font-size | -font-weight--hc-toggle-fg | -bg | -border--hc-toggle-hover-bg | -hover-fg--hc-toggle-on-bg | -on-fg | -on-border--hc-toggle-disabled-fg | -disabled-bg--hc-toggle-sm-* | -lg-*