Skip to content

Toggle

import { Toggle } from '@delightstack/components';
View code
<Toggle label="Dark Mode" />
View code
<Toggle label="Dark Mode" />

Dynamic text that changes based on the toggle state.

View code
<Toggle on_label="Notifications On" off_label="Notifications Off" />

Place the label before or after the toggle.

View code
<Toggle label="Setting" label_position="start" />
<Toggle label="Setting" label_position="end" />

Set indeterminate to add a third, in-between state. checked is then boolean | nullnull parks the thumb at a middle stop — and clicking cycles false → null → true → false. The track lengthens automatically so all three stops keep distinct touch targets. Useful for tri-state settings like allow / inherit / deny.

You can also drag the handle straight to any stop (e.g. null → false without cycling through true). Each stop has a magnetic pull while dragging, the track rubber-bands if you pull past its ends, and the handle springs into place on release. Arrow keys step between stops too.

checked = null — Only flagged comments are held for review
View code
<script>
import { Toggle } from '@delightstack/components';
// In indeterminate mode, checked is boolean | null
let moderation = $state(null);
</script>
<Toggle indeterminate bind:checked={moderation} label="Comment moderation" />

Use the thumb_icon snippet to render an icon inside the thumb.

View code
<script>
import { Toggle } from '@delightstack/components';
let sound = $state(true);
</script>
<Toggle bind:checked={sound} label={sound ? 'Sound On' : 'Sound Off'}>
{#snippet thumb_icon()}
{#if sound}
<!-- Volume icon (inline SVG) -->
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon><path d="M19.07 4.93a10 10 0 0 1 0 14.14"></path><path d="M15.54 8.46a5 5 0 0 1 0 7.07"></path></svg>
{:else}
<!-- Mute icon (inline SVG) -->
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon><line x1="23" y1="9" x2="17" y2="15"></line><line x1="17" y1="9" x2="23" y2="15"></line></svg>
{/if}
{/snippet}
</Toggle>

Use name and value for native form submission.

View code
<Toggle name="premium" value="true" label="Enable premium features" />
View code
<Toggle label="Disabled off" disabled />
<Toggle label="Disabled on" disabled checked />
View code
<Toggle size="0" label="Small (32x18)" />
<Toggle size="1" label="Default (44x24)" />
<Toggle size="2" label="Medium (52x28)" />
<Toggle size="3" label="Large (68x36)" />
View code
<script>
import { Toggle } from '@delightstack/components';
let settings = $state({
notifications: true,
autoSave: true,
sounds: false,
});
</script>
<div style="display: flex; flex-direction: column; gap: 1rem;">
<Toggle
bind:checked={settings.notifications}
label="Push Notifications"
name="notifications"
value="enabled"
/>
<Toggle
bind:checked={settings.autoSave}
label="Auto-save"
name="autosave"
value="enabled"
/>
<Toggle
bind:checked={settings.sounds}
label="Sound Effects"
name="sounds"
value="enabled"
/>
</div>
PropTypeDefaultDescription
checkedboolean (or boolean | null with indeterminate)-Toggle state, bindable. null is the in-between state (three-state mode only). When omitted inside a Form with a name, the state is context-driven (reads/writes the form data — no bind:checked needed)
indeterminatebooleanfalseEnable the third, in-between state (checked: null); clicking cycles false → null → true
disabledbooleanfalseDisable toggle
size'0' | '1' | '2' | '3''1'Toggle size
labelstring-Label text
label_position'start' | 'end''end'Label placement relative to toggle
on_labelstring-Label text when on
off_labelstring-Label text when off
namestring-Form field name. Inside a Form, registers the field and enables context-driven state
tristatebooleanfalseThree-state mode for tri-state form fields (same track as indeterminate): null/undefined mean “unanswered” (the middle stop), and the user can cycle back to it
default_checkedboolean-Default shown when the form data has no value yet (set by defaulted boolean form fields)
errorstring-Error message shown below the toggle (errors from a parent Form context display automatically)
parse(value: unknown) => unknown-Validator that throws a user-showable message; inside a Form it is registered with the form
valuestring-Form value when checked
tooltipstring-Tooltip text on hover
densebooleanfalseTighter label spacing
comfortablebooleanfalseMore label spacing
idstringautoElement ID
classstring''Additional CSS classes
thumb_iconSnippet-Custom icon rendered inside the thumb
EventDetailDescription
onchange{ checked: boolean } (or boolean | null with indeterminate)State changed
  • Hidden native <input type="checkbox"> for form submission and semantics (its indeterminate property is set while checked is null)
  • role="switch" on the custom element — role="checkbox" in indeterminate mode, since the switch role has no mixed state
  • aria-checked reflects toggle state ("mixed" while checked is null)
  • Full keyboard support: Space and Enter to toggle (cycles all three states in indeterminate mode); Arrow Left/Right step directly between stops
  • The thumb is draggable with pointer or touch, with magnetic snap points and spring physics
  • Focus ring visible on keyboard focus (:focus-visible)
  • Label associated via <label> element
  • Thumb slides with a spring-physics bounce animation (cubic-bezier(0.34, 1.4, 0.64, 1))
  • Press state widens the thumb for tactile feedback