Toggle
Native checkbox rendered as a switch with explicit label association, help text, and error messaging.
Examples
Default preview
Toggle is a native checkbox with role="switch": it submits a form value, toggles with Space, and
needs no JavaScript. The visible thumb is a peer-controlled sibling with a real border so the state remains
visible in forced-colors mode where background fills may disappear.
Default + help text
Send account and workflow notifications.
Receive occasional product updates.
Error + disabled
Accept the terms before continuing.
This setting cannot be changed by your role.
Form value
1<div class="space-y-8">
2 <p class="text-muted-foreground text-sm">
3 Toggle is a native checkbox with <code>role="switch"</code>: it submits a form value, toggles with Space, and
4 needs no JavaScript. The visible thumb is a peer-controlled sibling with a real border so the state remains
5 visible in forced-colors mode where background fills may disappear.
6 </p>
7
8 <section class="space-y-4">
9 {{ partial:docs/components/label content="Default + help text" }}
10 <div class="grid gap-4 md:grid-cols-2">
11 {{ partial:components/forms/toggle id="notifications" name="notifications" value="enabled" label="Notifications" }}
12 {{ slot:help }}Send account and workflow notifications.{{ /slot:help }}
13 {{ /partial:components/forms/toggle }}
14
15 {{ partial:components/forms/toggle id="marketing" name="marketing" value="yes" label="Marketing emails" checked="true" }}
16 {{ slot:help }}Receive occasional product updates.{{ /slot:help }}
17 {{ /partial:components/forms/toggle }}
18 </div>
19 </section>
20
21 <section class="space-y-4">
22 {{ partial:docs/components/label content="Error + disabled" }}
23 <div class="grid gap-4 md:grid-cols-2">
24 {{ partial:components/forms/toggle id="terms-switch" name="terms_switch" value="accepted" label="Accept terms" error="true" }}
25 {{ slot:error }}Accept the terms before continuing.{{ /slot:error }}
26 {{ /partial:components/forms/toggle }}
27
28 {{ partial:components/forms/toggle id="policy-switch" name="policy_switch" value="locked" label="Managed by organization policy" disabled="true" checked="true" }}
29 {{ slot:help }}This setting cannot be changed by your role.{{ /slot:help }}
30 {{ /partial:components/forms/toggle }}
31 </div>
32 </section>
33
34 <section class="space-y-4">
35 {{ partial:docs/components/label content="Form value" }}
36 <form class="space-y-3" action="/" method="get">
37 {{ partial:components/forms/toggle id="email-updates" name="email_updates" value="1" label="Email updates" checked="true" }}
38 {{ slot:help }}When checked, the submitted form data includes <code>email_updates=1</code>.{{ /slot:help }}
39 {{ /partial:components/forms/toggle }}
40 {{ partial:components/primitives/button label="Example submit" type="submit" size="sm" /}}
41 </form>
42 </section>
43</div>
<div class="space-y-8">
<p class="text-muted-foreground text-sm">
Toggle is a native checkbox with <code>role="switch"</code>: it submits a form value, toggles with Space, and
needs no JavaScript. The visible thumb is a peer-controlled sibling with a real border so the state remains
visible in forced-colors mode where background fills may disappear.
</p>
<section class="space-y-4">
{{ partial:docs/components/label content="Default + help text" }}
<div class="grid gap-4 md:grid-cols-2">
{{ partial:components/forms/toggle id="notifications" name="notifications" value="enabled" label="Notifications" }}
{{ slot:help }}Send account and workflow notifications.{{ /slot:help }}
{{ /partial:components/forms/toggle }}
{{ partial:components/forms/toggle id="marketing" name="marketing" value="yes" label="Marketing emails" checked="true" }}
{{ slot:help }}Receive occasional product updates.{{ /slot:help }}
{{ /partial:components/forms/toggle }}
</div>
</section>
<section class="space-y-4">
{{ partial:docs/components/label content="Error + disabled" }}
<div class="grid gap-4 md:grid-cols-2">
{{ partial:components/forms/toggle id="terms-switch" name="terms_switch" value="accepted" label="Accept terms" error="true" }}
{{ slot:error }}Accept the terms before continuing.{{ /slot:error }}
{{ /partial:components/forms/toggle }}
{{ partial:components/forms/toggle id="policy-switch" name="policy_switch" value="locked" label="Managed by organization policy" disabled="true" checked="true" }}
{{ slot:help }}This setting cannot be changed by your role.{{ /slot:help }}
{{ /partial:components/forms/toggle }}
</div>
</section>
<section class="space-y-4">
{{ partial:docs/components/label content="Form value" }}
<form class="space-y-3" action="/" method="get">
{{ partial:components/forms/toggle id="email-updates" name="email_updates" value="1" label="Email updates" checked="true" }}
{{ slot:help }}When checked, the submitted form data includes <code>email_updates=1</code>.{{ /slot:help }}
{{ /partial:components/forms/toggle }}
{{ partial:components/primitives/button label="Example submit" type="submit" size="sm" /}}
</form>
</section>
</div>
Props
| Name | Type | Default | Description |
|---|---|---|---|
id
|
string
|
|
Toggle id used by the label and described-by wiring (falls back to name/value) |
name
|
string
|
|
Name attribute used for form submissions |
value
|
string
|
|
Value attribute used for form submissions |
label
|
string
|
|
Switch label text |
checked
|
boolean
|
false
|
Checked state |
disabled
|
boolean
|
false
|
Disabled state |
error
|
boolean
|
false
|
Enables destructive visual state and aria-invalid |
class
|
string
|
|
Additional classes merged via tw_merge (root element only) |
Source
1{{#
2 @name Toggle
3 @desc Native checkbox rendered as a switch with explicit label association, help text, and error messaging.
4 @param id string - Toggle id used by the label and described-by wiring (falls back to name/value)
5 @param name string - Name attribute used for form submissions
6 @param value string - Value attribute used for form submissions
7 @param label string - Switch label text
8 @param checked boolean [false] - Checked state
9 @param disabled boolean [false] - Disabled state
10 @param error boolean [false] - Enables destructive visual state and aria-invalid
11 @param class string - Additional classes merged via tw_merge (root element only)
12 @slot help - Help text rendered below the toggle
13 @slot error - Error message rendered below the toggle and referenced by aria-describedby
14#}}
15{{ _help_slot = slot:help }}
16{{ _error_slot = slot:error }}
17{{ _field_id = id ?? '' }}
18{{ if !_field_id }}
19 {{ if name }}
20 {{ _field_id = name }}
21 {{ if value }}
22 {{ _field_id = '{name}-{value}' }}
23 {{ /if }}
24 {{ else }}
25 {{ _field_id = 'toggle' }}
26 {{ /if }}
27{{ /if }}
28{{ _describedby = '' }}
29{{ if _field_id }}
30 {{ if _error_slot }}
31 {{ _describedby = '{_field_id}-error' }}
32 {{ elseif _help_slot }}
33 {{ _describedby = '{_field_id}-help' }}
34 {{ /if }}
35{{ /if }}
36{{ _root_classes = 'space-y-1 {class}'
37 | tw_merge }}
38{{ _toggle_classes = 'peer border-foreground bg-background checked:border-primary checked:bg-primary aria-invalid:border-destructive focus-visible:border-focus-text focus-visible:outline-focus disabled:border-muted-foreground disabled:bg-muted h-6 w-10 shrink-0 appearance-none border-2 focus-visible:outline focus-visible:outline-4 disabled:cursor-not-allowed'
39 | tw_merge }}
40{{ _label_classes = 'text-foreground text-sm'
41 | tw_merge }}
42{{ if disabled }}
43 {{ _label_classes = 'text-muted-foreground text-sm'
44 | tw_merge }}
45{{ /if }}
46<div class="{{ _root_classes }}">
47 <div class="flex items-center gap-3">
48 <span class="relative flex h-6 w-10 shrink-0 items-center">
49 <input
50 class="{{ _toggle_classes }}"
51 type="checkbox"
52 role="switch"
53 {{ if name }}name="{{ name }}"{{ /if }}
54 {{ if value }}value="{{ value }}"{{ /if }}
55 id="{{ _field_id }}"
56 {{ if checked }}checked{{ /if }}
57 {{ if disabled }}disabled{{ /if }}
58 {{ if error }}aria-invalid="true"{{ /if }}
59 {{ if _describedby }}aria-describedby="{{ _describedby }}"{{ /if }}
60 />
61 <span
62 class="bg-foreground border-foreground peer-checked:bg-primary-foreground peer-checked:border-primary-foreground peer-disabled:bg-muted-foreground peer-disabled:border-muted-foreground pointer-events-none absolute top-1 left-1 size-4 border-2 transition-transform peer-checked:translate-x-4"
63 aria-hidden="true"
64 ></span>
65 </span>
66 <label class="{{ _label_classes }}" for="{{ _field_id }}">
67 {{ label }}
68 </label>
69 </div>
70 {{ if _error_slot }}
71 <p class="text-destructive text-sm font-bold" id="{{ _field_id }}-error">
72 {{ _error_slot }}
73 </p>
74 {{ elseif _help_slot }}
75 <p class="text-muted-foreground text-sm" id="{{ _field_id }}-help">
76 {{ _help_slot }}
77 </p>
78 {{ /if }}
79</div>
{{#
@name Toggle
@desc Native checkbox rendered as a switch with explicit label association, help text, and error messaging.
@param id string - Toggle id used by the label and described-by wiring (falls back to name/value)
@param name string - Name attribute used for form submissions
@param value string - Value attribute used for form submissions
@param label string - Switch label text
@param checked boolean [false] - Checked state
@param disabled boolean [false] - Disabled state
@param error boolean [false] - Enables destructive visual state and aria-invalid
@param class string - Additional classes merged via tw_merge (root element only)
@slot help - Help text rendered below the toggle
@slot error - Error message rendered below the toggle and referenced by aria-describedby
#}}
{{ _help_slot = slot:help }}
{{ _error_slot = slot:error }}
{{ _field_id = id ?? '' }}
{{ if !_field_id }}
{{ if name }}
{{ _field_id = name }}
{{ if value }}
{{ _field_id = '{name}-{value}' }}
{{ /if }}
{{ else }}
{{ _field_id = 'toggle' }}
{{ /if }}
{{ /if }}
{{ _describedby = '' }}
{{ if _field_id }}
{{ if _error_slot }}
{{ _describedby = '{_field_id}-error' }}
{{ elseif _help_slot }}
{{ _describedby = '{_field_id}-help' }}
{{ /if }}
{{ /if }}
{{ _root_classes = 'space-y-1 {class}'
| tw_merge }}
{{ _toggle_classes = 'peer border-foreground bg-background checked:border-primary checked:bg-primary aria-invalid:border-destructive focus-visible:border-focus-text focus-visible:outline-focus disabled:border-muted-foreground disabled:bg-muted h-6 w-10 shrink-0 appearance-none border-2 focus-visible:outline focus-visible:outline-4 disabled:cursor-not-allowed'
| tw_merge }}
{{ _label_classes = 'text-foreground text-sm'
| tw_merge }}
{{ if disabled }}
{{ _label_classes = 'text-muted-foreground text-sm'
| tw_merge }}
{{ /if }}
<div class="{{ _root_classes }}">
<div class="flex items-center gap-3">
<span class="relative flex h-6 w-10 shrink-0 items-center">
<input
class="{{ _toggle_classes }}"
type="checkbox"
role="switch"
{{ if name }}name="{{ name }}"{{ /if }}
{{ if value }}value="{{ value }}"{{ /if }}
id="{{ _field_id }}"
{{ if checked }}checked{{ /if }}
{{ if disabled }}disabled{{ /if }}
{{ if error }}aria-invalid="true"{{ /if }}
{{ if _describedby }}aria-describedby="{{ _describedby }}"{{ /if }}
/>
<span
class="bg-foreground border-foreground peer-checked:bg-primary-foreground peer-checked:border-primary-foreground peer-disabled:bg-muted-foreground peer-disabled:border-muted-foreground pointer-events-none absolute top-1 left-1 size-4 border-2 transition-transform peer-checked:translate-x-4"
aria-hidden="true"
></span>
</span>
<label class="{{ _label_classes }}" for="{{ _field_id }}">
{{ label }}
</label>
</div>
{{ if _error_slot }}
<p class="text-destructive text-sm font-bold" id="{{ _field_id }}-error">
{{ _error_slot }}
</p>
{{ elseif _help_slot }}
<p class="text-muted-foreground text-sm" id="{{ _field_id }}-help">
{{ _help_slot }}
</p>
{{ /if }}
</div>
Dependencies
Packages
marcorieser/tailwind-merge-statamic
1composer require marcorieser/tailwind-merge-statamic
composer require marcorieser/tailwind-merge-statamic
Internal dependencies
- Slots: help, error