AttrToggleGroup
A generic toggle bound to a data-* attribute on <html> and a localStorage key. It hydrates from storage on mount, sanitises stale persisted values back to the configured default, and offers two visual variants: a segmented button row or a Select dropdown. Use it to control any axis your product needs (theme, palette, density, language, currency, …) without coupling the component to a specific use case.
Import
Segmented variant
Default visual: a Memphis-bordered button row with aria-pressed on each option and a role="group" wrapper labelled by the eyebrow.
Select variant
Use when the option list is too long for a button row, or when the surface is space-constrained.
Without an eyebrow label
Drop the label prop when the surrounding UI already provides a heading.
Sanitisation
If a previously persisted value is no longer in options (e.g. you renamed a palette), the hook resets it to defaultValue on mount, then writes the corrected value back to localStorage and to the data-* attribute. The user never sees a flash of stale state.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| options* | AttrToggleOption[] | — | List of { value, label } pairs the user can pick from. |
| storageKey* | string | — | localStorage key that persists the active value across sessions. |
| attribute* | string | — | data-* attribute mirrored on <html>. Must match ^data-[a-z][a-z0-9-]*$ — non-data attributes are rejected. |
| defaultValue | string | — | Initial value when nothing is persisted. Falls back to options[0].value. |
| label | string | — | Optional eyebrow label rendered before the control. When provided, the inner group is wired with aria-labelledby to the eyebrow id. |
| variant | 'segmented' | 'select' | 'segmented' | segmented is a Memphis-bordered button row; select swaps to the design-system Select dropdown. |
| labelId | string | — | Override the auto-generated id for the eyebrow label. Use when you need to wire your own external label. |
| className | string | — | Tailwind classes merged on top of the wrapper defaults. |
Accessibility
- The segmented variant wraps options in
role="group"; whenlabelis set, the group is labelled viaaria-labelledbypointing to the eyebrow id. - Each segmented button is a native
<button>witharia-pressedreflecting the active state. - The select variant exposes
role="combobox"via the design-systemSelectprimitive — full keyboard interaction is inherited.