ACTIONS & SURFACES

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

import { AttrToggleGroup } from 'damo-ui'

Segmented variant

Default visual: a Memphis-bordered button row with aria-pressed on each option and a role="group" wrapper labelled by the eyebrow.

Tone
1<AttrToggleGroup
2  label="Tone"
3  variant="segmented"
4  options={[
5    { value: 'soft', label: 'Soft' },
6    { value: 'bold', label: 'Bold' },
7    { value: 'mono', label: 'Mono' },
8  ]}
9  storageKey="docs-tone"
10  attribute="data-docs-tone"
11  defaultValue="soft"
12/>

Select variant

Use when the option list is too long for a button row, or when the surface is space-constrained.

Palette
1<AttrToggleGroup
2  label="Palette"
3  variant="select"
4  options={[
5    { value: 'plum', label: 'Plum' },
6    { value: 'cyberpunk', label: 'Cyberpunk' },
7    { value: 'sunset', label: 'Sunset' },
8  ]}
9  storageKey="docs-palette"
10  attribute="data-docs-palette"
11  defaultValue="plum"
12/>

Without an eyebrow label

Drop the label prop when the surrounding UI already provides a heading.

1<AttrToggleGroup
2  // Omit `label` to render just the control — useful when the
3  // surrounding UI already provides a heading.
4  options={DENSITY_OPTIONS}
5  storageKey="density"
6  attribute="data-density"
7  defaultValue="normal"
8/>

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

AttrToggleGroup props
PropTypeDefaultDescription
options*AttrToggleOption[]List of { value, label } pairs the user can pick from.
storageKey*stringlocalStorage key that persists the active value across sessions.
attribute*stringdata-* attribute mirrored on <html>. Must match ^data-[a-z][a-z0-9-]*$ — non-data attributes are rejected.
defaultValuestringInitial value when nothing is persisted. Falls back to options[0].value.
labelstringOptional 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.
labelIdstringOverride the auto-generated id for the eyebrow label. Use when you need to wire your own external label.
classNamestringTailwind classes merged on top of the wrapper defaults.

Accessibility

  • The segmented variant wraps options in role="group"; when label is set, the group is labelled via aria-labelledby pointing to the eyebrow id.
  • Each segmented button is a native <button> with aria-pressed reflecting the active state.
  • The select variant exposes role="combobox" via the design-system Select primitive — full keyboard interaction is inherited.