Toggle
Universal toggle component with two semantic shapes:
switchβ binary on/off (one boolean setting). Usesrole="switch",aria-checked.segmentedβ mutually exclusive options (2+ values). Usesrole="group"witharia-current="page"for navigation links, orrole="radio"+aria-checkedfor state-only buttons.
Powers language switchers, theme pickers, filters, view-mode toggles β anything where the UI shape is βpick exactly oneβ.
Toggle.astrois Astro-only (uses<script>). Import via subpath, not the barrel.
Switch β boolean filter
The gbx-toolbar βEU market onlyβ filter pattern: a single setting, on/off, with
a visible label next to the track.
---
import Toggle from 'spoko-design-system/components/Toggle.astro';
---<Toggle variant="switch" label="EU market only" checked name="eu-only" />Off state
<Toggle variant="switch" label="Show archived" name="show-archived" />Label position
<Toggle variant="switch" label="Notifications" checked labelPosition="before" /><Toggle variant="switch" label="Notifications" checked labelPosition="after" />Sizes
<Toggle variant="switch" size="sm" label="Small" checked /><Toggle variant="switch" size="md" label="Medium" checked /><Toggle variant="switch" size="lg" label="Large" checked />Disabled
<Toggle variant="switch" label="Feature locked" disabled />Segmented β language switcher
Navigation-style switcher where each option is an anchor with a pre-resolved URL.
Active option marked with aria-current="page". Pass reload for stacks where
view transitions canβt safely swap the locale (forces window.location.href).
<Toggle
variant="segmented"
groupLabel="Language"
current="en"
reload
items={[
{ value: 'en', label: 'EN', href: '/gearboxes/', hreflang: 'en' },
{ value: 'pl', label: 'PL', href: '/pl/skrzynie-biegow/', hreflang: 'pl' },
{ value: 'de', label: 'DE', href: '/de/getriebe/', hreflang: 'de' },
]}
/>Segmented β state-only (theme picker)
When items have no href, the component renders <button role="radio"> with
arrow-key navigation between options and emits a toggle-change CustomEvent.
<Toggle
variant="segmented"
groupLabel="Theme"
current="auto"
items={[
{ value: 'light', label: 'Light' },
{ value: 'dark', label: 'Dark' },
{ value: 'auto', label: 'Auto' },
]}
/>Segmented β view mode
<Toggle
variant="segmented"
groupLabel="View"
current="grid"
items={[
{ value: 'list', label: 'List' },
{ value: 'grid', label: 'Grid' },
{ value: 'compact', label: 'Compact' },
]}
/>Segmented β sizes
<Toggle variant="segmented" size="sm" groupLabel="Range" current="d"
items={[
{ value: 'd', label: 'Day' },
{ value: 'w', label: 'Week' },
{ value: 'm', label: 'Month' },
]} /><Toggle variant="segmented" size="md" groupLabel="Range" current="d"
items={[
{ value: 'd', label: 'Day' },
{ value: 'w', label: 'Week' },
{ value: 'm', label: 'Month' },
]} /><Toggle variant="segmented" size="lg" groupLabel="Range" current="d"
items={[
{ value: 'd', label: 'Day' },
{ value: 'w', label: 'Week' },
{ value: 'm', label: 'Month' },
]} />Segmented β with disabled option
<Toggle
variant="segmented"
groupLabel="Plan"
current="pro"
items={[
{ value: 'free', label: 'Free' },
{ value: 'pro', label: 'Pro' },
{ value: 'enterprise', label: 'Enterprise', disabled: true },
]}
/>Styled looks
Three opt-in recipes ported from real-world toggles. Apply via look prop.
look="pill" β solid-fill segmented (catalog-style)
White pill container with a thin tinted border. Active option is filled solid with the darkest blue and white text β strong visual emphasis, no shadow trick.
look="spoko" β transparent pill with dark active fill
Transparent container with a softer outline. Active option uses a near-black navy on light backgrounds and inverts to near-white in dark mode.
look="outline" β switch wrapped in an outlined frame
The catalog toggler pattern β track + thumb + label sit inside a rounded
border that tints toward the accent on hover and turns into the accent colour
when on. Useful when the switch needs to read as a discrete chip rather than a
bare slider.
<Toggle
variant="switch"
look="outline"
checked
label="EU market only"
/><Toggle
variant="switch"
look="outline"
label="Show archived"
/>Listening to changes
Both variants dispatch a bubbling toggle-change CustomEvent.
document.addEventListener('toggle-change', (e) => {
const detail = (e as CustomEvent).detail;
// switch: { name?, value?, checked: boolean }
// segmented: { value: string }
console.log(detail);
});
Accessibility
All colour pairs in the default style meet WCAG AA contrast (β₯4.5:1 for text, β₯3:1 for UI components):
| Surface | Pair | Ratio |
|---|---|---|
Active segmented option (bg-white + text-blue-medium #02307d) | text vs background | β 15:1 |
Inactive segmented option (bg-slate-100 + text-slate-dark #334155) | text vs background | β 9:1 |
Switch ON track (bg-blue-medium + white thumb) | thumb vs track | β 15:1 |
Switch OFF track (bg-slate-light #64748B + white thumb) | thumb vs track | β 4.7:1 |
| Disabled (opacity 0.5) | text vs background | β₯ 4.5:1 |
ARIA contract:
- Switch:
role="switch",aria-checkedtoggled on click/space/enter,disabledpropagated, focus ring via:focus-visible. - Segmented (links):
role="group"+aria-label, active item getsaria-current="page", hreflang propagated when supplied. - Segmented (radios):
role="group"+aria-label, each optionrole="radio"+aria-checked, only the active option is tab-stop (otherstabindex="-1"),β/βmoves focus and updates selection.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
variant | 'switch' | 'segmented' | 'switch' | Toggle shape β boolean switch or mutually-exclusive options |
size | 'sm' | 'md' | 'lg' | 'md' | Visual size of the control (only honored by `look="default"`) |
look | 'default' | 'pill' | 'spoko' | 'outline' | 'default' | Visual style. `pill` and `spoko` apply to segmented; `outline` applies to switch |
checked | boolean | false | [switch] Initial on/off state |
label | string | undefined | [switch] Visible label rendered next to the track |
labelPosition | 'before' | 'after' | 'after' | [switch] Whether the label sits before or after the track |
disabled | boolean | false | [switch] Disable interaction |
name | string | undefined | [switch] Identifier emitted in toggle-change detail |
value | string | undefined | [switch] Value emitted in toggle-change detail |
items | SegmentedItem[] | [] | [segmented] Options β each `{ value, label, href?, hreflang?, disabled? }` |
current | string | undefined | [segmented] Value of the active option |
groupLabel | string | 'Options' | [segmented] aria-label for the group |
reload | boolean | false | [segmented] Force `window.location.href` on link clicks β use when view transitions cannot handle the swap (e.g. locale changes) |
class | string | '' | Extra classes on the root element |
CSS recipe classes
| Class | Purpose |
|---|---|
sds-toggle-switch | Root of a switch β flex container with label + track |
sds-toggle-track | Rounded track holding the thumb |
sds-toggle-thumb | Sliding white circle inside the track |
sds-toggle-label | Visible label text |
sds-toggle-segmented | Root of a segmented group |
sds-toggle-seg-btn | Individual option β link or button |