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.

---
import { Switch } from "@opui/astro"
---
<Switch checked hideLabel>Label</Switch>
<Switch hideLabel>Label</Switch>
<Switch checked disabled hideLabel>Label</Switch>
<Switch disabled hideLabel>Label</Switch>
<label class="switch">
<input type="checkbox" role="switch" checked /><span class="sr-only"
>Label</span
>
</label>
<label class="switch">
<input type="checkbox" role="switch" /><span class="sr-only">Label</span>
</label>
<label class="switch">
<input type="checkbox" role="switch" checked disabled /><span class="sr-only"
>Label</span
>
</label>
<label class="switch">
<input type="checkbox" role="switch" disabled /><span class="sr-only"
>Label</span
>
</label>

Visible label

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

---
import { Switch } from "@opui/astro"
---
<Switch>Label</Switch>
<Switch disabled>Disabled</Switch>
<Switch>
Long text bacon ipsum dolor amet prosciutto tenderloin biltong leberkas ribeye
short ribs shankle tri-tip doner buffalo chislic meatloaf meatball.
</Switch>
<label class="switch">
<input 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

---
import { Switch } from "@opui/astro"
---
<Switch>Default</Switch>
<Switch stack>Stack</Switch>
<label class="switch">
<input type="checkbox" role="switch" /><span class="label">Default</span>
</label>
<label class="switch stack">
<input type="checkbox" role="switch" /><span class="label">Stack</span>
</label>

End text

---
import { Switch } from "@opui/astro"
---
<Switch>
Default
<Fragment slot="end-text">Supporting text</Fragment>
</Switch>
<Switch stack>
Stack
<Fragment slot="end-text">Supporting text</Fragment>
</Switch>
<label class="switch">
<input type="checkbox" role="switch" aria-describedby="end-text-1" /><span
class="label"
>
Default
</span>
<span id="end-text-1" class="end-text">Supporting text</span>
</label>
<label class="switch stack">
<input type="checkbox" role="switch" aria-describedby="end-text-2" /><span
class="label"
>
Stack
</span>
<span id="end-text-2" 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.
---
import { Switch } from "@opui/astro"
---
<div class="example-row spacious">
<Switch required>Default</Switch>
<Switch required stack>Stack</Switch>
</div>
<div class="example-row spacious">
<Switch critical>
Default
<Fragment slot="end-text">Supporting text</Fragment>
</Switch>
<Switch critical stack>
Stack
<Fragment slot="end-text">Supporting text</Fragment>
</Switch>
</div>
<div class="example-row spacious">
<label class="switch">
<input type="checkbox" role="switch" required /><span class="label"
>Default</span
>
</label>
<label class="switch stack">
<input type="checkbox" role="switch" required /><span class="label"
>Stack</span
>
</label>
</div>
<div class="example-row spacious">
<label class="switch" data-invalid="true">
<input type="checkbox" role="switch" aria-describedby="end-text-3" /><span
class="label"
>
Default
</span>
<span id="end-text-3" class="end-text">Supporting text</span>
</label>
<label class="switch stack" data-invalid="true">
<input type="checkbox" role="switch" aria-describedby="end-text-4" /><span
class="label"
>
Stack
</span>
<span id="end-text-4" class="end-text">Supporting text</span>
</label>
</div>

Spread

Use the spread prop to push the label to the left and the switch to the right. This is useful for full-width items like lists and menus.

---
import { Switch } from "@opui/astro"
---
<Switch spread>
Notifications
<Fragment slot="end-text">Receive alerts when someone mentions you.</Fragment>
</Switch>
<Switch spread required>
Required
<Fragment slot="end-text">You must accept this to proceed.</Fragment>
</Switch>
<Switch spread disabled>
Disabled
<Fragment slot="end-text">This switch is disabled.</Fragment>
</Switch>
<Switch spread critical>
Invalid Switch
<Fragment slot="end-text">There is an error with this switch.</Fragment>
</Switch>
<label class="switch spread">
<input type="checkbox" role="switch" aria-describedby="end-text-5" /><span
class="label"
>
Notifications
</span>
<span id="end-text-5" class="end-text"
>Receive alerts when someone mentions you.</span
>
</label>
<label class="switch spread">
<input
type="checkbox"
role="switch"
required
aria-describedby="end-text-6"
/><span class="label"> Required </span>
<span id="end-text-6" class="end-text">You must accept this to proceed.</span>
</label>
<label class="switch spread">
<input
type="checkbox"
role="switch"
disabled
aria-describedby="end-text-7"
/><span class="label"> Disabled </span>
<span id="end-text-7" class="end-text">This switch is disabled.</span>
</label>
<label class="switch spread" data-invalid="true">
<input type="checkbox" role="switch" aria-describedby="end-text-8" /><span
class="label"
>
Invalid Switch
</span>
<span id="end-text-8" class="end-text"
>There is an error with this switch.</span
>
</label>

Sizes

Set the small prop for a smaller Switch variant.

---
import { Switch } from "@opui/astro"
---
<div class="example-row">
<Switch small checked hideLabel>Small</Switch>
<Switch checked hideLabel>Default</Switch>
</div>
<div class="example-row">
<Switch small checked>Small</Switch>
<Switch checked>Default</Switch>
</div>
<div class="example-row">
<label class="switch small">
<input type="checkbox" role="switch" checked /><span class="sr-only"
>Small</span
>
</label>
<label class="switch">
<input type="checkbox" role="switch" checked /><span class="sr-only"
>Default</span
>
</label>
</div>
<div class="example-row">
<label class="switch small">
<input type="checkbox" role="switch" checked /><span class="label"
>Small</span
>
</label>
<label class="switch">
<input type="checkbox" role="switch" checked /><span class="label"
>Default</span
>
</label>
</div>

Icons

---
import { Switch } from "@opui/astro"
---
<Switch small aria-label="Toggle theme">
<svg
slot="icon-unchecked"
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
>
<svg
slot="icon-checked"
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
>
</Switch>
<Switch checked aria-label="Toggle theme">
<svg
slot="icon-unchecked"
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
>
<svg
slot="icon-checked"
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
>
</Switch>
<label class="switch small">
<span class="icon-unchecked" aria-hidden="true">
<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" aria-hidden="true">
<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 type="checkbox" role="switch" aria-label="Toggle theme" />
</label>
<label class="switch">
<span class="icon-unchecked" aria-hidden="true">
<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" aria-hidden="true">
<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 type="checkbox" role="switch" checked aria-label="Toggle theme" />
</label>

Field group

Use field groups to group related switches.

The name prop will get passed down to each switch in the group.

See also: Form documentation.

Legend
---
import { Switch } from "@opui/astro"
import { FieldSet } from "@opui/astro"
import { FieldLegend } from "@opui/astro"
import { FieldGroup } from "@opui/astro"
import { Form } from "@opui/astro"
---
<Form as="div">
<FieldSet>
<FieldLegend>Legend</FieldLegend>
<FieldGroup name="switch-group-astro">
<Switch>Switch 1</Switch>
<Switch>Switch 2</Switch>
<Switch>Switch 3</Switch>
</FieldGroup>
</FieldSet>
</Form>
<div class="form">
<fieldset class="fieldset">
<legend>Legend</legend>
<div class="field-group" role="group">
<label class="switch">
<input type="checkbox" role="switch" name="switch-group-astro" /><span
class="label"
>Switch 1</span
>
</label>
<label class="switch">
<input type="checkbox" role="switch" name="switch-group-astro" /><span
class="label"
>Switch 2</span
>
</label>
<label class="switch">
<input type="checkbox" role="switch" name="switch-group-astro" /><span
class="label"
>Switch 3</span
>
</label>
</div>
</fieldset>
</div>

Direction

Legend
---
import { Switch } from "@opui/astro"
import { FieldSet } from "@opui/astro"
import { FieldLegend } from "@opui/astro"
import { FieldGroup } from "@opui/astro"
import { Form } from "@opui/astro"
---
<Form>
<FieldSet>
<FieldLegend>Legend</FieldLegend>
<FieldGroup direction="row" name="switch-group-direction-astro">
<Switch>Switch 1</Switch>
<Switch>Switch 2</Switch>
<Switch>Switch 3</Switch>
</FieldGroup>
</FieldSet>
</Form>
<form class="form">
<fieldset class="fieldset">
<legend>Legend</legend>
<div class="field-group row" role="group">
<label class="switch">
<input
type="checkbox"
role="switch"
name="switch-group-direction-astro"
/><span class="label">Switch 1</span>
</label>
<label class="switch">
<input
type="checkbox"
role="switch"
name="switch-group-direction-astro"
/><span class="label">Switch 2</span>
</label>
<label class="switch">
<input
type="checkbox"
role="switch"
name="switch-group-direction-astro"
/><span class="label">Switch 3</span>
</label>
</div>
</fieldset>
</form>

Field description

Can be placed above and below the fields.

Legend

Field description above fields

Legend

Field description below fields

---
import { Switch } from "@opui/astro"
import { FieldSet } from "@opui/astro"
import { FieldLegend } from "@opui/astro"
import { FieldDescription } from "@opui/astro"
import { FieldGroup } from "@opui/astro"
import { Form } from "@opui/astro"
---
<Form>
<FieldSet>
<FieldLegend>Legend</FieldLegend>
<FieldDescription>Field description above fields</FieldDescription>
<FieldGroup direction="row" name="switch-group-field-description-1-astro">
<Switch>Switch 1</Switch>
<Switch>Switch 2</Switch>
<Switch>Switch 3</Switch>
</FieldGroup>
</FieldSet>
<FieldSet>
<FieldLegend>Legend</FieldLegend>
<FieldGroup direction="row" name="switch-group-field-description-2-astro">
<Switch>Switch 1</Switch>
<Switch>Switch 2</Switch>
<Switch>Switch 3</Switch>
</FieldGroup>
<FieldDescription>Field description below fields</FieldDescription>
</FieldSet>
</Form>
<form class="form">
<fieldset class="fieldset">
<legend>Legend</legend>
<p class="field-description">Field description above fields</p>
<div class="field-group row" role="group">
<label class="switch">
<input
type="checkbox"
role="switch"
name="switch-group-field-description-1-astro"
/><span class="label">Switch 1</span>
</label>
<label class="switch">
<input
type="checkbox"
role="switch"
name="switch-group-field-description-1-astro"
/><span class="label">Switch 2</span>
</label>
<label class="switch">
<input
type="checkbox"
role="switch"
name="switch-group-field-description-1-astro"
/><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
type="checkbox"
role="switch"
name="switch-group-field-description-2-astro"
/><span class="label">Switch 1</span>
</label>
<label class="switch">
<input
type="checkbox"
role="switch"
name="switch-group-field-description-2-astro"
/><span class="label">Switch 2</span>
</label>
<label class="switch">
<input
type="checkbox"
role="switch"
name="switch-group-field-description-2-astro"
/><span class="label">Switch 3</span>
</label>
</div>
<p class="field-description">Field description below fields</p>
</fieldset>
</form>

Disabled

Attach the disabled attribute to the <fieldset> element.

Legend
---
import { Switch } from "@opui/astro"
import { FieldSet } from "@opui/astro"
import { FieldLegend } from "@opui/astro"
import { FieldGroup } from "@opui/astro"
import { Form } from "@opui/astro"
---
<Form>
<FieldSet disabled>
<FieldLegend>Legend</FieldLegend>
<FieldGroup direction="row" name="switch-group-disabled-astro">
<Switch>Switch 1</Switch>
<Switch>Switch 2</Switch>
<Switch>Switch 3</Switch>
</FieldGroup>
</FieldSet>
</Form>
<form class="form">
<fieldset disabled class="fieldset">
<legend>Legend</legend>
<div class="field-group row" role="group">
<label class="switch">
<input
type="checkbox"
role="switch"
name="switch-group-disabled-astro"
/><span class="label">Switch 1</span>
</label>
<label class="switch">
<input
type="checkbox"
role="switch"
name="switch-group-disabled-astro"
/><span class="label">Switch 2</span>
</label>
<label class="switch">
<input
type="checkbox"
role="switch"
name="switch-group-disabled-astro"
/><span class="label">Switch 3</span>
</label>
</div>
</fieldset>
</form>

Required

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

These are required!
---
import { Switch } from "@opui/astro"
import { FieldSet } from "@opui/astro"
import { FieldLegend } from "@opui/astro"
import { FieldGroup } from "@opui/astro"
import { Form } from "@opui/astro"
---
<Form>
<FieldSet>
<FieldLegend>These are required!</FieldLegend>
<FieldGroup direction="row" name="switch-group-required-astro">
<Switch required>Switch 1</Switch>
<Switch required>Switch 2</Switch>
<Switch required>Switch 3</Switch>
</FieldGroup>
</FieldSet>
</Form>
<form class="form">
<fieldset class="fieldset">
<legend>These are required!</legend>
<div class="field-group row" role="group">
<label class="switch">
<input
type="checkbox"
role="switch"
name="switch-group-required-astro"
required
/><span class="label">Switch 1</span>
</label>
<label class="switch">
<input
type="checkbox"
role="switch"
name="switch-group-required-astro"
required
/><span class="label">Switch 2</span>
</label>
<label class="switch">
<input
type="checkbox"
role="switch"
name="switch-group-required-astro"
required
/><span class="label">Switch 3</span>
</label>
</div>
</fieldset>
</form>

Validation

Attach the data-invalid attribute to your Fieldset component.

Legend
Something went wrong!
---
import { Switch } from "@opui/astro"
import { FieldSet } from "@opui/astro"
import { FieldLegend } from "@opui/astro"
import { FieldGroup } from "@opui/astro"
import { Form } from "@opui/astro"
---
<Form>
<FieldSet data-invalid>
<FieldLegend>Legend</FieldLegend>
<FieldGroup direction="row" name="switch-group-validation-astro">
<Switch>Switch 1</Switch>
<Switch>Switch 2</Switch>
<Switch>Switch 3</Switch>
</FieldGroup>
<span class="end-text">Something went wrong!</span>
</FieldSet>
</Form>
<form class="form">
<fieldset data-invalid="true" class="fieldset">
<legend>Legend</legend>
<div class="field-group row" role="group">
<label class="switch">
<input
type="checkbox"
role="switch"
name="switch-group-validation-astro"
/><span class="label">Switch 1</span>
</label>
<label class="switch">
<input
type="checkbox"
role="switch"
name="switch-group-validation-astro"
/><span class="label">Switch 2</span>
</label>
<label class="switch">
<input
type="checkbox"
role="switch"
name="switch-group-validation-astro"
/><span class="label">Switch 3</span>
</label>
</div>
<span class="end-text">Something went wrong!</span>
</fieldset>
</form>

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

API

Switch API

Prop Type Default Description
aria-label string - Accessible name for the switch. Use when there's no visible label (ex icon-only switches).
aria-labelledby string - ID of an external element that labels the switch.
checked boolean false Whether the switch is checked.
disabled boolean false Whether the switch is disabled.
critical boolean false Switch error state.
hideLabel boolean false Visually hides the label but keeps it accessible.
required boolean false Whether the switch is required.
small boolean false Reduces the size of the switch.
spread boolean false Spreads the label and switch to opposite ends.
stack boolean false Labels are stacked under the element.

Slots

Slot Description
default The label text for the switch.
end-text Optional supporting text displayed after the label.

Field group API

Prop Type Default Description
critical boolean false Field group error state.
legend string - The text for the legend element.
direction "row" - The orientation of the field group.

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);
}
}
}
}