Toggle
Accessible on/off switches with labels, sizes, intent-based styling, and micro-interactions.
Playground
<Toggle
checked
helper="Push updates instantly"
label="Enable notifications"
/>01 Examples
Notification Preferences
Notification Preferences
Manage how you receive updates
<div
class="bg-surface-elevated border-border-subtle w-full overflow-hidden rounded-2xl border"
>
<div class="border-border-subtle border-b px-5 py-3">
<h3 class="text-text-primary text-sm font-semibold">Notification Preferences</h3>
<p class="text-text-tertiary text-xs">Manage how you receive updates</p>
</div>
<div class="divide-border-subtle divide-y px-5">
<div class="py-3">
<Toggle
bind:checked={notifications}
label="Push Notifications"
helper="Receive alerts on your device"
intent="primary"
/>
</div>
<div class="py-3">
<Toggle
bind:checked={autoSave}
label="Email Digest"
helper="Weekly summary of your activity"
intent="primary"
/>
</div>
<div class="py-3">
<Toggle
bind:checked={readReceipts}
label="Read Receipts"
helper="Let others know when you've seen their messages"
intent="primary"
/>
</div>
</div>
</div>Mint micro-interactions
<Toggle mint="scale" label="Scale on hover" checked />
<Toggle mint="glow" label="Glow on hover" checked intent="success" />
<Toggle mint={['scale', 'glow']} label="Combined scale + glow" checked intent="danger" />02 Customization
Gradient Tracks
<Toggle
checked
label="Premium Mode"
slotClasses={{
track:
'bg-linear-to-r from-violet-500 to-fuchsia-500 shadow-lg shadow-violet-500/25 border-transparent'
}}
/>
<Toggle
checked
label="Eco Mode"
slotClasses={{
track:
'bg-linear-to-r from-emerald-500 to-teal-400 shadow-lg shadow-emerald-500/25 border-transparent'
}}
/>
<Toggle
checked
label="Sunset Mode"
slotClasses={{
track:
'bg-linear-to-r from-orange-500 to-rose-500 shadow-lg shadow-orange-500/25 border-transparent'
}}
/>Dark Mode Switch
<div
class="bg-surface-elevated border-border-subtle inline-flex items-center gap-4 rounded-xl border px-5 py-3"
>
<SunIcon size={20} class="text-text-secondary" />
<Toggle
bind:checked={darkMode}
intent="neutral"
size="lg"
slotClasses={{
track: darkMode
? 'bg-linear-to-r from-indigo-600 to-violet-700 shadow-lg shadow-indigo-500/30 border-transparent'
: ''
}}
/>
<MoonIcon size={20} class="text-text-secondary" />
</div>Fully Custom (unstyled)
<Toggle
unstyled
checked
label="SYSTEM_ACTIVE"
slotClasses={{
control:
'inline-flex cursor-pointer items-center gap-3 font-mono text-sm text-emerald-400 select-none',
track:
'relative h-6 w-12 rounded border border-emerald-500/50 bg-emerald-950/50 transition-colors data-[state=checked]:bg-emerald-500/20 data-[state=checked]:border-emerald-400',
thumb:
'absolute left-0.5 top-1/2 -translate-y-1/2 h-4.5 w-4.5 rounded-sm bg-emerald-500 transition-all data-[state=checked]:translate-x-6 data-[state=checked]:shadow-[0_0_12px_rgba(16,185,129,0.6)]'
}}
/>
<Toggle
unstyled
label="NETWORK_IO"
slotClasses={{
control:
'inline-flex cursor-pointer items-center gap-3 font-mono text-sm text-emerald-300 select-none',
track:
'relative h-6 w-12 rounded border border-emerald-500/30 bg-emerald-950/30 transition-colors data-[state=checked]:bg-emerald-500/20 data-[state=checked]:border-emerald-400',
thumb:
'absolute left-0.5 top-1/2 -translate-y-1/2 h-4.5 w-4.5 rounded-sm bg-emerald-500/40 transition-all data-[state=checked]:translate-x-6 data-[state=checked]:bg-emerald-500 data-[state=checked]:shadow-[0_0_12px_rgba(16,185,129,0.6)]'
}}
/>A brand track treatment reused across settings belongs in a BlocksProvider preset (presets.Toggle), applied via preset — see Customization.
03 Accessibility
Built-in ARIA
Renders with role="switch" and aria-checked that updates automatically. Labels are
associated via id, and helper text is linked through aria-describedby.
Keyboard
Tab to focus, Space to toggle. The focus ring uses peer-focus-visible: to relay the hidden input's focus state
onto the visible track.
Reduced Motion
The thumb slide animation and all Mint effects are suppressed when prefers-reduced-motion is enabled.
Don't wrap with <label>
Toggle already renders a correctly associated <label> internally. Wrapping it in another <label> creates
nested label semantics — clicks on the outer label may not toggle the switch reliably across browsers,
and screen readers can announce the label twice.
Don't
<label>
Notifications
<Toggle />
</label>Do
<Toggle label="Notifications"
helper="Email + push" />04 API Reference
Prop | Type | Default | Description | |
|---|---|---|---|---|
checked | boolean | — | Current on/off state. Supports two-way binding via bind:checked. | |
class | string | — | Extra classes merged onto the outermost wrapper element. | |
disabled | boolean | — | Prevent interaction and dim the control. | |
error | string | — | Error message shown below the control. Replaces helper, flags the
hidden input aria-invalid, and marks the unchecked track with the
danger colour — e.g. for a consent toggle that must be switched on. | |
helper | string | — | Helper text shown below the control. Hidden when error is set. Useful for explaining side-effects of the toggle. | |
id | string | — | Explicit id to link <label> and <input>. Auto-generated if omitted. | |
intent variant | dangerneutralprimarysecondary +2 more | primary | Controls the color theme and semantic meaning of the Toggle. Affects the overall appearance and user perception. Available options: danger, neutral, primary, and 3 more. | |
label | string | — | Text label displayed to the right of the toggle track. | |
mint | MintProp | 'none' | Micro-interaction preset applied to the track. Only applies while not disabled. | |
name | string | — | The name attribute of the underlying <input>. Used for form submission. | |
onCheckedChange | (checked: boolean) => void | — | Fired after the checked state changes. Receives the new checked value. | |
preset | string | — | Apply a named preset registered via <BlocksProvider presets={{ Toggle: {...} }}>.
Prefer this over class overrides when the requested look falls outside the
semantic intent palette — presets keep hover/active/dark-mode logic coherent
and make the custom look reusable across the project. | |
required | boolean | — | Mark the native input as required for form validation. | |
size variant | lgmdsmxs | md | Controls the dimensions, padding, and text size of the Toggle. Affects the component's physical footprint. Available options: lg, md, sm, xs. | |
slotClasses | Partial<Record<ToggleSlots, string>> | — | Per-slot class overrides merged with (or replacing, when unstyled) the
default styles. Slots: wrapper (root — what class also targets) | control
(the <label> wrapping the input) | track | thumb | label | message. | |
tier | InteractiveTier | — | Semantic radius tier. Default commit — a toggle declares status
(on/off identity), and reads as a pill. Set to modify (usually via a
wrapping <Toolbar tier="modify"> propagating through TierContext) for
inline-toolbar contexts where the Pill feels oversized — the track and
thumb shrink to a soft-rectangle.
Inherited from TierContext when omitted; falls back to commit outside
of any tier-aware container. | |
unstyled | boolean | — | Strip all default tailwind-variants classes. Use with slotClasses for a fully custom look. The track exposes data-state for conditional styling. | |
value | string | — | The value submitted when checked. Defaults to 'on'. | |
variant | ToggleVariants['variant'] | 'default' | Visual style. default renders a classic Switch-Pill (track + sliding
thumb). dot renders a small monochrome circular indicator instead —
outline-only when off, filled in the intent colour when on. Use dot
for dense settings rows, inline-toolbars, or anywhere the Switch-Pill
is visually too loud. | |
withBorder | boolean | — | Draw a subtle border around the track. Helps distinguish the control on busy backgrounds. | |
...HTMLInputAttributes inherited | HTMLAttributes | — | HTML attributes (excluding: 'type' | 'size' | 'checked' | 'class' | 'children') | |
...ToggleVariants variant | VariantProps | — | Styling variants from ToggleVariants |
05 Types
Local type definitions used by this component.
Name | Kind | Category | Used by | Description | |
|---|---|---|---|---|---|
ToggleProps | interface | props | 0 | — | |
ToggleVariants | type | variant | 0 | — | |
ToggleSlots | type | variant | 0 | Slot names derived from the tv() config above — single source of truth for slotClasses. | |
MintProp | type | helper | 1 | — | |
InteractiveTier | type | helper | 1 | Semantic radius tier for interactive surfaces (3-tier system).
- commit → r-human (CTA, identity, status declarations)
- modify → r-interactive (fields, navigation, secondary actions)
Container components (Card, Alert, Toolbar surface, …) live in a third
tier contain (r-structure) which is **not** part of this propagation
context — those surfaces are always r-structure by design and have no
tier-flip use case. | |
MintName | type | helper | 0 | A mint name: a built-in (autocompleted), 'none' to disable, or any
consumer-registered name. (string & {}) keeps the registry open — a
custom name still type-checks, it just isn't suggested. A typo therefore
also still compiles (it resolves like an unregistered custom name and
warns at runtime); the union buys completion and docs, not validation. | |
MintConfig | interface | helper | 0 | — | |
BuiltinMintName | type | helper | 0 | Built-in mint names as a literal union, so the mint prop autocompletes
across every component — the single list the hand-curated playground knobs
and docs used to drift away from. |
06 Installation
Import
import { Toggle } from '@urbicon-ui/blocks';