Skip to main content

Theme config

Theme mode

Color palette

Grays

Border radii/radiuses/radiopedes/you know
Border radius
Field border radius
Button border radius
All
Components
Guides
API
Recent

Components

Switch

See also: Switch field group.

Full support Supported since v123. Full support Supported since v121. Full support Supported since v17.5.

All switches should have an accessible name. Either provide a visible or visually-hidden label inside the component, or set aria-label on the input. Both approaches are fine.

<!-- Checked -->
<label class="switch">
<input name="switch-variants-html" type="checkbox" role="switch" checked aria-label="Label" />
</label>
<!-- Unchecked -->
<label class="switch">
<input name="switch-variants-html" type="checkbox" role="switch" aria-label="Label" />
</label>
<!-- Checked & disabled -->
<label class="switch">
<input name="switch-variants-html" type="checkbox" role="switch" checked disabled aria-label="Label" />
</label>
<!-- Unchecked & disabled -->
<label class="switch">
<input name="switch-variants-html" type="checkbox" role="switch" disabled aria-label="Label" />
</label>

Visible label

Render the label text inside an element with a .label class. Also, don't miss the info on label accessibility.

<label class="switch">
<input name="switch-visible-label-html" type="checkbox" role="switch" />
<span class="label">Label</span>
</label>
<label class="switch">
<input type="checkbox" role="switch" disabled />
<span class="label">Disabled</span>
</label>
<label class="switch">
<input type="checkbox" role="switch" />
<span class="label">Long text bacon ipsum dolor amet prosciutto tenderloin biltong leberkas ribeye short ribs shankle
tri-tip doner buffalo chislic meatloaf meatball.</span>
</label>

Label position

<label class="switch">
<input name="switch-label-position-html" type="checkbox" role="switch" />
<span class="label">Default</span>
</label>
<label class="switch stack">
<input name="switch-label-position-html" type="checkbox" role="switch" />
<span class="label">Stack</span>
</label>

End text

<label class="switch">
<input name="switch-supporting-text-html" type="checkbox" role="switch" />
<span class="label">Default</span>
<span class="end-text">Supporting text</span>
</label>
<label class="switch stack">
<input name="switch-supporting-text-html" type="checkbox" role="switch" />
<span class="label">Stack</span>
<span class="end-text">Supporting text</span>
</label>

Validation

  • Add [required] to the <input> element to toggle required styles
  • The data-invalid attribute toggles the error styles. Make use of the end text to give extra feedback on the error.
<div class="example-row spacious">
<label class="switch">
<input name="switch-validation-html" type="checkbox" role="switch" required />
<span class="label">Default</span>
</label>
<label class="switch stack">
<input name="switch-validation-html" type="checkbox" role="switch" required />
<span class="label">Stack</span>
</label>
</div>
<div class="example-row spacious">
<label class="switch" data-invalid>
<input name="switch-validation-html" type="checkbox" role="switch" />
<span class="label">Default</span>
<span class="end-text">Supporting text</span>
</label>
<label class="switch stack" data-invalid>
<input name="switch-validation-html" type="checkbox" role="switch" />
<span class="label">Stack</span>
<span class="end-text">Supporting text</span>
</label>
</div>

Spread

Add the .spread class to the <label.switch> to push the label to the left and the switch to the right. This is useful for full-width items like lists and menus.

<label class="switch spread">
<input name="switch-spread-html" type="checkbox" role="switch" />
<span class="label">Notifications</span>
<span class="end-text">Receive alerts when someone mentions you.</span>
</label>
<label class="switch spread">
<input name="switch-spread-html" type="checkbox" role="switch" required />
<span class="label">Required</span>
<span class="end-text">You must accept this to proceed.</span>
</label>
<label class="switch spread">
<input name="switch-spread-html" type="checkbox" role="switch" disabled />
<span class="label">Disabled</span>
<span class="end-text">This switch is disabled.</span>
</label>
<label class="switch spread critical">
<input name="switch-spread-html" type="checkbox" role="switch" />
<span class="label">Invalid Switch</span>
<span class="end-text">There is an error with this switch.</span>
</label>

Sizes

Add the .small class on the <label.switch> for a smaller Switch variant.

<div class="example-row">
<label class="switch small">
<input name="switch-sizes-html" type="checkbox" role="switch" checked aria-label="Small" />
</label>
<label class="switch">
<input name="switch-sizes-html" type="checkbox" role="switch" checked aria-label="Default" />
</label>
</div>
<div class="example-row">
<label class="switch small">
<input name="switch-sizes-html" type="checkbox" role="switch" checked />
<span class="label">Small</span>
</label>
<label class="switch">
<input name="switch-sizes-html" type="checkbox" role="switch" checked />
<span class="label">Default</span>
</label>
</div>

Icons

<label class="switch small">
<span class="icon-unchecked">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path fill="currentColor"
d="M20.026 17.001c-2.762 4.784-8.879 6.423-13.663 3.661A10 10 0 0 1 3.13 17.68a.75.75 0 0 1 .365-1.132c3.767-1.348 5.785-2.91 6.956-5.146c1.233-2.353 1.551-4.93.689-8.463a.75.75 0 0 1 .769-.927a9.96 9.96 0 0 1 4.457 1.327c4.784 2.762 6.423 8.879 3.66 13.662">
</path>
</svg>
</span>
<span class="icon-checked">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path fill="currentColor"
d="M12 2a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 12 2m5 10a5 5 0 1 1-10 0a5 5 0 0 1 10 0m4.25.75a.75.75 0 0 0 0-1.5h-1.5a.75.75 0 0 0 0 1.5zM12 19a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 12 19m-7.75-6.25a.75.75 0 0 0 0-1.5h-1.5a.75.75 0 0 0 0 1.5zm-.03-8.53a.75.75 0 0 1 1.06 0l1.5 1.5a.75.75 0 0 1-1.06 1.06l-1.5-1.5a.75.75 0 0 1 0-1.06m1.06 15.56a.75.75 0 1 1-1.06-1.06l1.5-1.5a.75.75 0 1 1 1.06 1.06zm14.5-15.56a.75.75 0 0 0-1.06 0l-1.5 1.5a.75.75 0 0 0 1.06 1.06l1.5-1.5a.75.75 0 0 0 0-1.06m-1.06 15.56a.75.75 0 1 0 1.06-1.06l-1.5-1.5a.75.75 0 1 0-1.06 1.06z">
</path>
</svg>
</span>
<input name="switch-icons-html" type="checkbox" role="switch" aria-label="Toggle theme" />
</label>
<label class="switch">
<span class="icon-unchecked">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path fill="currentColor"
d="M20.026 17.001c-2.762 4.784-8.879 6.423-13.663 3.661A10 10 0 0 1 3.13 17.68a.75.75 0 0 1 .365-1.132c3.767-1.348 5.785-2.91 6.956-5.146c1.233-2.353 1.551-4.93.689-8.463a.75.75 0 0 1 .769-.927a9.96 9.96 0 0 1 4.457 1.327c4.784 2.762 6.423 8.879 3.66 13.662">
</path>
</svg>
</span>
<span class="icon-checked">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path fill="currentColor"
d="M12 2a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 12 2m5 10a5 5 0 1 1-10 0a5 5 0 0 1 10 0m4.25.75a.75.75 0 0 0 0-1.5h-1.5a.75.75 0 0 0 0 1.5zM12 19a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 12 19m-7.75-6.25a.75.75 0 0 0 0-1.5h-1.5a.75.75 0 0 0 0 1.5zm-.03-8.53a.75.75 0 0 1 1.06 0l1.5 1.5a.75.75 0 0 1-1.06 1.06l-1.5-1.5a.75.75 0 0 1 0-1.06m1.06 15.56a.75.75 0 1 1-1.06-1.06l1.5-1.5a.75.75 0 1 1 1.06 1.06zm14.5-15.56a.75.75 0 0 0-1.06 0l-1.5 1.5a.75.75 0 0 0 1.06 1.06l1.5-1.5a.75.75 0 0 0 0-1.06m-1.06 15.56a.75.75 0 1 0 1.06-1.06l-1.5-1.5a.75.75 0 1 0-1.06 1.06z">
</path>
</svg>
</span>
<input name="switch-icons-html" type="checkbox" role="switch" checked aria-label="Toggle theme" />
</label>

Field group

Use field groups to group related switches.

Give every <input> in the group the same name attribute so they're submitted together.

See also: Form documentation.

Legend
<fieldset class="fieldset">
<legend>Legend</legend>
<div class="field-group" role="group">
<label class="switch">
<input name="switch-group-html" type="checkbox" role="switch" />
<span class="label">Switch 1</span>
</label>
<label class="switch">
<input name="switch-group-html" type="checkbox" role="switch" />
<span class="label">Switch 2</span>
</label>
<label class="switch">
<input name="switch-group-html" type="checkbox" role="switch" />
<span class="label">Switch 3</span>
</label>
</div>
</fieldset>

Direction

Legend
<fieldset class="fieldset">
<legend>Legend</legend>
<div class="field-group row" role="group">
<label class="switch">
<input name="switch-group-direction-html" type="checkbox" role="switch" />
<span class="label">Switch 1</span>
</label>
<label class="switch">
<input name="switch-group-direction-html" type="checkbox" role="switch" />
<span class="label">Switch 2</span>
</label>
<label class="switch">
<input name="switch-group-direction-html" type="checkbox" role="switch" />
<span class="label">Switch 3</span>
</label>
</div>
</fieldset>

Field description

Can be placed above and below the fields.

Legend Field description above fields
Legend
Field description below fields
<fieldset class="fieldset">
<legend>Legend</legend>
<span class="field-description">Field description above fields</span>
<div class="field-group row" role="group">
<label class="switch">
<input name="switch-group-field-description-1-html" type="checkbox" role="switch" />
<span class="label">Switch 1</span>
</label>
<label class="switch">
<input name="switch-group-field-description-1-html" type="checkbox" role="switch" />
<span class="label">Switch 2</span>
</label>
<label class="switch">
<input name="switch-group-field-description-1-html" type="checkbox" role="switch" />
<span class="label">Switch 3</span>
</label>
</div>
</fieldset>
<fieldset class="fieldset">
<legend>Legend</legend>
<div class="field-group row" role="group">
<label class="switch">
<input name="switch-group-field-description-2-html" type="checkbox" role="switch" />
<span class="label">Switch 1</span>
</label>
<label class="switch">
<input name="switch-group-field-description-2-html" type="checkbox" role="switch" />
<span class="label">Switch 2</span>
</label>
<label class="switch">
<input name="switch-group-field-description-2-html" type="checkbox" role="switch" />
<span class="label">Switch 3</span>
</label>
</div>
<span class="field-description">Field description below fields</span>
</fieldset>

Disabled

Attach the disabled attribute to the <fieldset> element.

Legend
<fieldset class="fieldset" disabled>
<legend>Legend</legend>
<div class="field-group row" role="group">
<label class="switch">
<input name="switch-group-disabled-html" type="checkbox" role="switch" />
<span class="label">Switch 1</span>
</label>
<label class="switch">
<input name="switch-group-disabled-html" type="checkbox" role="switch" />
<span class="label">Switch 2</span>
</label>
<label class="switch">
<input name="switch-group-disabled-html" type="checkbox" role="switch" />
<span class="label">Switch 3</span>
</label>
</div>
</fieldset>

Required

Attach the required attribute to at least one of your <input> elements.

These are required!
<fieldset class="fieldset">
<legend>These are required!</legend>
<div class="field-group row" role="group">
<label class="switch">
<input name="switch-group-required-html" type="checkbox" role="switch" required />
<span class="label">Switch 1</span>
</label>
<label class="switch">
<input name="switch-group-required-html" type="checkbox" role="switch" required />
<span class="label">Switch 2</span>
</label>
<label class="switch">
<input name="switch-group-required-html" type="checkbox" role="switch" required />
<span class="label">Switch 3</span>
</label>
</div>
</fieldset>

Validation

Attach the data-invalid attribute to your fieldset.fieldset element.

Legend
Something went wrong!
<fieldset class="fieldset" data-invalid>
<legend>Legend</legend>
<div class="field-group row" role="group">
<label class="switch">
<input name="switch-field-group-validation-html" type="checkbox" role="switch" />
<span class="label">Switch 1</span>
</label>
<label class="switch">
<input name="switch-field-group-validation-html" type="checkbox" role="switch" />
<span class="label">Switch 2</span>
</label>
<label class="switch">
<input name="switch-field-group-validation-html" type="checkbox" role="switch" />
<span class="label">Switch 3</span>
</label>
</div>
<span class="end-text">Something went wrong!</span>
</fieldset>

Accessibility

Role & attributes

Role/attribute Usage
role="switch" Required on the input element. Identifies the element that serves as a switch.

Labels

Accessible switches should have a label. The first two approaches are equally ok:

Approach Usage in Switch component
Provide a label inside the element Use a .label child for a visible label, or a .sr-only child to hide it visually while keeping it accessible. In Astro, set the hideLabel prop to render the slot content as .sr-only.
Add an aria-label on the input Used when there's no visible label inside the component (e.g. icon-only switches). In Astro, pass aria-label as a prop on the component and it will land on the input.
Have a visible label that you reference with aria-labelledby Not used.

Keyboard support

Key Function
Space When Switch is focused it changes its state.
Enter (Optional) When Switch is focused it changes its state.

Anatomy

  1. Container: label element
  2. Switch: & input type="checkbox" role="switch"
  3. Label (optional): & .label
  4. End text (optional): .end-text
<label class="switch anatomy">
<input type="checkbox" role="switch" aria-describedby="end-text-1" /><span
class="label"
>
Label
</span>
<span id="end-text-1" class="end-text">End text</span>
</label>

API

Switch API

Type Modifiers Default Description
Children .label, .end-text, .icon-unchecked, .icon-checked - Optional children.
Sizes .small, default - The size of the element.
Layout .spread, .stack, default - The layout of the switch. .spread pushes label and switch to opposite ends. .stack stacks the label under the switch.
Validation .critical - When applied, error styles are shown.

Field group API

Type Modifiers Default Description
Children <legend>, .legend, .fields, .end-text, checkbox, radio, switch - Supported child elements.
Direction default, .row - The orientation of the element.
Disabled [disabled] - When applied, disabled styles are shown.
Validation .critical - When applied, error styles are shown.

Browser support

Full support Supported since v123. Full support Supported since v121. Full support Supported since v17.5.

See also the full browser support guide.

Installation

See also

@layer components.root {
:where(.switch) {
--_accent-color: var(--primary);
--_accent-contrast: var(--primary-contrast);
--_dot-bg-color: light-dark(var(--gray-11), var(--gray-14));
--_dot-inset: var(--size-1) auto auto var(--size-1);
--_dot-outline-size: 0;
--_dot-size: var(--size-3);
--_track-bg-color: light-dark(var(--gray-3), var(--gray-8));
--_track-height: var(--size-5);
--_track-width: var(--size-8);
--_transition-tf: var(--ease-4);
--_transition-time: 0.2s;
align-items: center;
color: var(--text-primary);
display: inline-grid;
gap: 0 var(--size-2);
grid-auto-columns: auto;
grid-auto-flow: column;
inline-size: fit-content;
:where(input[type="checkbox"][role="switch"]) {
appearance: none;
block-size: var(--_track-height);
cursor: pointer;
inline-size: var(--_track-width);
margin: 0;
position: relative;
/* Track */
&::before {
background-color: var(--_track-bg-color);
block-size: var(--_track-height);
border: 1px solid var(--_dot-bg-color);
border-radius: var(--radius-round);
content: "";
inline-size: var(--_track-width);
inset: 0;
position: absolute;
}
&:focus-visible {
--focus-ring-color: currentColor;
outline: var(--focus-ring-width) var(--focus-ring-style) var(--focus-ring-color);
outline-offset: var(--focus-ring-offset);
}
/* Dot */
&::after {
background-color: var(--_dot-bg-color);
block-size: var(--_dot-size);
border-radius: var(--radius-round);
content: "";
inline-size: var(--_dot-size);
inset: var(--_dot-inset);
outline: var(--_dot-outline-size) solid var(--_dot-bg-color);
outline-offset: -1px;
position: absolute;
}
/* Checked */
&:checked {
&::before {
background-color: var(--_accent-color);
border-color: var(--_accent-color);
transition:
background-color var(--_transition-time) var(--_transition-tf),
border-color var(--_transition-time) var(--_transition-tf);
}
/* Dot */
&::after {
--_dot-bg-color: var(--_accent-contrast);
--_dot-outline-size: calc(var(--size-1) - 1px);
inset-inline-start: calc(var(--_track-width) - var(--_dot-size) - var(--size-1));
}
}
/* Animation */
@media (prefers-reduced-motion: no-preference) {
/* Track */
&::before {
transition:
background-color var(--_transition-time) var(--_transition-tf),
border-color var(--_transition-time) var(--_transition-tf);
}
/* Dot */
&::after {
transition: all var(--_transition-time) var(--_transition-tf);
}
&:active:after {
--_dot-outline-size: calc(var(--size-1) + 1px);
}
&:checked {
&:active:after {
--_dot-outline-size: calc(var(--size-1) + 1px);
}
}
}
}
/* Icons */
&:has(.icon-checked, .icon-unchecked) {
[role="switch"] {
grid-column: 1 / -1;
grid-row: 1;
}
&:has([role="switch"]:checked) {
.icon-unchecked {
display: none;
}
.icon-checked {
display: block;
}
}
.icon-checked {
display: none;
}
.icon-unchecked,
.icon-checked {
grid-column: 1;
grid-row: 1;
pointer-events: none;
z-index: 1;
svg {
display: block;
inline-size: var(--_dot-size);
}
}
.icon-unchecked {
margin-inline-start: calc(var(--_track-width) - var(--_dot-size) - var(--size-1));
}
.icon-checked {
margin-inline-start: var(--size-1);
}
}
/* Required dot */
&:has(:invalid) {
.label:after {
color: var(--red);
content: "*";
inset: 0 -0.25ex auto auto;
position: absolute;
}
}
/* Disabled */
&:has([disabled]) {
cursor: not-allowed;
opacity: 0.64;
user-select: none;
input {
cursor: not-allowed;
}
}
/* Label */
.label {
color: var(--text-primary);
font-size: var(--font-size-05);
grid-column: 2;
grid-row: 1;
min-width: 0;
padding-inline: 0 1ex;
position: relative;
user-select: none;
}
/* End text */
:where(.end-text) {
color: var(--text-muted);
font-size: var(--font-size-0);
grid-column: 2;
grid-row: 2;
line-height: 1.5;
z-index: 1;
}
/* Size */
&.small {
--_dot-size: 0.75rem;
--_track-height: var(--size-4);
--_track-width: 2.5rem;
}
/* Spread layout */
&.spread {
align-items: center;
column-gap: var(--size-4);
grid-auto-flow: unset;
grid-template-columns: 1fr auto;
inline-size: 100%;
.label {
font-weight: 600;
grid-column: 1;
grid-row: 1;
inline-size: fit-content;
}
input[role="switch"] {
grid-column: 2;
grid-row: 1;
}
&:has(.icon-checked, .icon-unchecked) {
input[role="switch"],
.icon-checked,
.icon-unchecked {
grid-column: 2;
}
}
:where(.end-text) {
grid-column: 1;
grid-row: 2;
}
}
/* Stacked layout */
&.stack {
grid-auto-columns: unset;
justify-items: center;
.label {
grid-column: 1/-1;
grid-row: 2;
margin-block-start: var(--size-1);
padding-inline: 1ex;
}
.end-text {
grid-column: 1/-1;
grid-row: 3;
}
}
/* Validation */
&[data-invalid] {
input {
border-radius: var(--radius-round);
outline: 2px solid var(--color-9);
}
.end-text {
color: var(--color-9);
}
}
}
}
@layer components.extended {
/* Form */
:where(.form) {
display: grid;
gap: var(--size-8);
hr {
margin-block: 0;
}
}
/* Fieldset */
:where(.fieldset) {
all: unset;
border: 0;
border-radius: 0;
display: grid;
gap: var(--size-1);
margin: 0;
min-inline-size: 0;
padding: 0;
/* Legend */
:where(legend, .legend) {
all: unset;
color: var(--text-primary);
font-weight: 600;
margin-block-end: var(--size-3);
padding: 0;
&:has(+ :where(.field-description)) {
margin-block-end: 0;
}
}
/* Field description / supporting text */
:where(.field-description) {
color: var(--text-muted);
font-size: var(--font-size-05);
line-height: var(--font-lineheight-3);
&:has(+ *) {
margin-block-end: var(--size-3);
}
}
/* End text */
:where(.end-text) {
color: var(--text-muted);
font-size: var(--font-size-0);
line-height: var(--font-lineheight-3);
}
&:has(.text-field.row) {
row-gap: var(--size-7);
}
/* Disabled */
&[disabled] {
opacity: 0.64;
user-select: none;
input,
label,
.label {
cursor: not-allowed;
}
}
/* Error / validation */
&[data-invalid] {
:where(.end-text) {
color: var(--primary);
}
:where(.checkbox, .radio, .switch) {
--palette-source: oklch(0.58 0.21 var(--hue-red));
:where(.end-text) {
color: var(--primary);
}
}
:where(.switch) {
input {
border-radius: var(--radius-round);
outline: 2px solid var(--primary);
}
}
}
/* Required */
&:has(:invalid) {
:where(legend, .legend) {
padding-inline-end: 1ex;
position: relative;
&::after {
color: var(--red);
content: "*";
inset: 0 -0.25ex auto auto;
position: absolute;
}
}
}
}
/* Field group */
:where(.field-group) {
display: flex;
flex-direction: column;
gap: var(--size-4);
/* If it only has checkboxes, radios, or switches */
&:has(> :where(.checkbox, .radio, .switch)):not(:has(> :not(.checkbox, .radio, .switch))) {
gap: var(--size-2);
}
/* If it only has buttons */
&:has(.button):not(:has(*:not(.button))) {
align-items: center;
flex-direction: row;
gap: var(--size-2);
/* If it's not preceeded by an hr */
&:not(hr + &) {
margin-block-start: var(--size-4);
}
}
&+& {
margin-block-start: var(--size-5);
}
/* Directions */
&.row {
flex-direction: row;
flex-wrap: wrap;
&:has(> :where(.checkbox, .radio, .switch)) {
gap: var(--size-4);
}
}
}
}