Toggle
An on/off switch for a single setting.
Playground
<Toggle
checked
helper="Push updates instantly"
label="Enable notifications"
/>01 Examples
Notification preferences
onCheckedChange receives the new boolean right after the user flips a switch, which is where persisting it belongs. It rides the input's change event, so a checked value you assign in code moves the switch without calling it.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"
/>
</div>
<div class="py-3">
<Toggle
bind:checked={emailDigest}
label="Email Digest"
helper="Weekly summary of your activity"
/>
</div>
<div class="py-3">
<Toggle
bind:checked={readReceipts}
label="Read Receipts"
helper="Let others know when you've seen their messages"
/>
</div>
</div>
</div>Submitted with a form
name submits the switch as value (on by default) for as long as it is on. An off switch stays out of the FormData altogether, so read the presence of the key rather than a true or false.<form class="flex w-full flex-col gap-3" onsubmit={handleSubmit}>
<Toggle name="beta" label="Join the beta channel" />
<Toggle name="telemetry" label="Share anonymous usage data" checked />
<Button type="submit" size="sm" class="self-start">Save</Button>
<p class="text-text-tertiary text-xs">
Submitted keys: <code class="text-text-primary">{saved ?? 'nothing yet'}</code>
</p>
</form>Mint micro-interactions
<Toggle mint="glow" label="Glow on hover" checked intent="success" />
<Toggle mint={['scale', 'glow']} label="Scale and glow together" checked intent="danger" />02 Customization
Night-sky track
data-state of checked or unchecked, so the gradient hangs off the on-state instead of a ternary in your markup.<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}
aria-label="Dark mode"
intent="neutral"
size="lg"
slotClasses={{
track:
'data-[state=checked]:border-transparent data-[state=checked]:bg-linear-to-r data-[state=checked]:from-indigo-600 data-[state=checked]:to-violet-700 data-[state=checked]:shadow-lg data-[state=checked]:shadow-indigo-500/30'
}}
/>
<MoonIcon size={20} class="text-text-secondary" />
</div>If every switch in the app should share a track treatment, set it once as a defaults entry for Toggle on a BlocksProvider. A preset is the opt-in variant of the same thing: it
reaches only the switches that name it through their preset prop.
This is one of five ways to restyle a block. See Customization for class, slotClasses, unstyled, preset and provider-level overrides.
03 Accessibility
Built-in ARIA
The input is a checkbox with role="switch" and an aria-checked that follows the state. A label names it, helper and error text reach it
through aria-describedby, and an error sets aria-invalid. Where a design carries no visible text,
the switch falls back to a translated generic name, so pass your own aria-label instead.
Keyboard
Tab to focus, Space to toggle. The focus ring shows for keyboard users only and sits on the track.
Reduced motion
Every Mint effect is switched off under prefers-reduced-motion, and the thumb slide collapses
to a millisecond along with every other duration token.
Don't wrap with <label>
Toggle renders its own associated <label> around track and text, and HTML has no meaning for a second one wrapped around that. Give the
switch its text through the label prop instead.
Don't
<label>
Notifications
<Toggle />
</label>Do
<Toggle label="Notifications"
helper="Email + push" />04 API Reference
22 propsProp | 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 variant 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 of the control. Reach for dot where the switch pill is
visually too loud — dense settings rows, inline toolbars. | |
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';