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.

<script setup lang="ts">
import { Switch } from "opui-css/vue"
</script>
<template>
<Switch checked hideLabel>Label</Switch>
<Switch hideLabel>Label</Switch>
<Switch checked disabled hideLabel>Label</Switch>
<Switch disabled hideLabel>Label</Switch>
</template>
<!--[--><label class="ui-switch"
><!----><!----><input
type="checkbox"
role="switch"
aria-describedby="s0-0"
/><span class="ui-sr-only"><!--[-->Label<!--]--></span
><!----></label
><label class="ui-switch"
><!----><!----><input
type="checkbox"
role="switch"
aria-describedby="s0-1"
/><span class="ui-sr-only"><!--[-->Label<!--]--></span
><!----></label
><label class="ui-switch"
><!----><!----><input
type="checkbox"
role="switch"
disabled
aria-describedby="s0-2"
/><span class="ui-sr-only"><!--[-->Label<!--]--></span
><!----></label
><label class="ui-switch"
><!----><!----><input
type="checkbox"
role="switch"
disabled
aria-describedby="s0-3"
/><span class="ui-sr-only"><!--[-->Label<!--]--></span
><!----></label
><!--]-->

Visible label

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

<script setup lang="ts">
import { Switch } from "opui-css/vue"
</script>
<template>
<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>
</template>
<!--[--><label class="ui-switch"
><!----><!----><input
type="checkbox"
role="switch"
aria-describedby="s1-0"
/><span class="ui-label"><!--[-->Label<!--]--></span
><!----></label
><label class="ui-switch"
><!----><!----><input
type="checkbox"
role="switch"
disabled
aria-describedby="s1-1"
/><span class="ui-label"><!--[-->Disabled<!--]--></span
><!----></label
><label class="ui-switch"
><!----><!----><input
type="checkbox"
role="switch"
aria-describedby="s1-2"
/><span class="ui-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

<script setup lang="ts">
import { Switch } from "opui-css/vue"
</script>
<template>
<Switch>Default</Switch>
<Switch stack>Stack</Switch>
</template>
<!--[--><label class="ui-switch"
><!----><!----><input
type="checkbox"
role="switch"
aria-describedby="s2-0"
/><span class="ui-label"><!--[-->Default<!--]--></span
><!----></label
><label class="ui-switch ui-stack"
><!----><!----><input
type="checkbox"
role="switch"
aria-describedby="s2-1"
/><span class="ui-label"><!--[-->Stack<!--]--></span
><!----></label
><!--]-->

End text

<script setup lang="ts">
import { Switch } from "opui-css/vue"
</script>
<template>
<Switch>
Default
<template #end-text>Supporting text</template>
</Switch>
<Switch stack>
Stack
<template #end-text>Supporting text</template>
</Switch>
</template>
<!--[--><label class="ui-switch"
><!----><!----><input
type="checkbox"
role="switch"
aria-describedby="s3-0"
/><span class="ui-label"
><!--[-->
Default
<!--]--></span
><span id="s3-0" class="ui-end-text"
><!--[-->Supporting text<!--]--></span
></label
><label class="ui-switch ui-stack"
><!----><!----><input
type="checkbox"
role="switch"
aria-describedby="s3-1"
/><span class="ui-label"
><!--[-->
Stack
<!--]--></span
><span id="s3-1" class="ui-end-text"
><!--[-->Supporting text<!--]--></span
></label
><!--]-->

Validation

  • Add the required attribute on the component. It is forwarded to the underlying <input>.
  • Use the error prop to toggle invalid styles. It renders data-invalid on the root element. Make use of the end text to give extra feedback on the error.
<script setup lang="ts">
import { Switch } from "opui-css/vue"
</script>
<template>
<div class="example-row ui-spacious">
<Switch required>Default</Switch>
<Switch required stack>Stack</Switch>
</div>
<div class="example-row ui-spacious">
<Switch error>
Default
<template #end-text>Supporting text</template>
</Switch>
<Switch error stack>
Stack
<template #end-text>Supporting text</template>
</Switch>
</div>
</template>
<!--[-->
<div class="example-row ui-spacious">
<label class="ui-switch"
><!----><!----><input
type="checkbox"
role="switch"
required
aria-describedby="s4-0"
/><span class="ui-label"><!--[-->Default<!--]--></span
><!----></label
><label class="ui-switch ui-stack"
><!----><!----><input
type="checkbox"
role="switch"
required
aria-describedby="s4-1"
/><span class="ui-label"><!--[-->Stack<!--]--></span
><!----></label
>
</div>
<div class="example-row ui-spacious">
<label class="ui-switch" data-invalid="true"
><!----><!----><input
type="checkbox"
role="switch"
aria-describedby="s4-2"
/><span class="ui-label"
><!--[-->
Default
<!--]--></span
><span id="s4-2" class="ui-end-text"
><!--[-->Supporting text<!--]--></span
></label
><label class="ui-switch ui-stack" data-invalid="true"
><!----><!----><input
type="checkbox"
role="switch"
aria-describedby="s4-3"
/><span class="ui-label"
><!--[-->
Stack
<!--]--></span
><span id="s4-3" class="ui-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.

<script setup lang="ts">
import { Switch } from "opui-css/vue"
</script>
<template>
<Switch spread>
Notifications
<template #end-text>Receive alerts when someone mentions you.</template>
</Switch>
<Switch spread required>
Required
<template #end-text>You must accept this to proceed.</template>
</Switch>
<Switch spread disabled>
Disabled
<template #end-text>This switch is disabled.</template>
</Switch>
<Switch spread error>
Invalid Switch
<template #end-text>There is an error with this switch.</template>
</Switch>
</template>
<!--[--><label class="ui-switch ui-spread"
><!----><!----><input
type="checkbox"
role="switch"
aria-describedby="s5-0"
/><span class="ui-label"
><!--[-->
Notifications
<!--]--></span
><span id="s5-0" class="ui-end-text"
><!--[-->Receive alerts when someone mentions you.<!--]--></span
></label
><label class="ui-switch ui-spread"
><!----><!----><input
type="checkbox"
role="switch"
required
aria-describedby="s5-1"
/><span class="ui-label"
><!--[-->
Required
<!--]--></span
><span id="s5-1" class="ui-end-text"
><!--[-->You must accept this to proceed.<!--]--></span
></label
><label class="ui-switch ui-spread"
><!----><!----><input
type="checkbox"
role="switch"
disabled
aria-describedby="s5-2"
/><span class="ui-label"
><!--[-->
Disabled
<!--]--></span
><span id="s5-2" class="ui-end-text"
><!--[-->This switch is disabled.<!--]--></span
></label
><label class="ui-switch ui-spread" data-invalid="true"
><!----><!----><input
type="checkbox"
role="switch"
aria-describedby="s5-3"
/><span class="ui-label"
><!--[-->
Invalid Switch
<!--]--></span
><span id="s5-3" class="ui-end-text"
><!--[-->There is an error with this switch.<!--]--></span
></label
><!--]-->

Sizes

Set the small prop for a smaller Switch variant.

<script setup lang="ts">
import { Switch } from "opui-css/vue"
</script>
<template>
<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>
</template>
<!--[-->
<div class="example-row">
<label class="ui-switch ui-small"
><!----><!----><input
type="checkbox"
role="switch"
aria-describedby="s6-0"
/><span class="ui-sr-only"><!--[-->Small<!--]--></span
><!----></label
><label class="ui-switch"
><!----><!----><input
type="checkbox"
role="switch"
aria-describedby="s6-1"
/><span class="ui-sr-only"><!--[-->Default<!--]--></span
><!----></label
>
</div>
<div class="example-row">
<label class="ui-switch ui-small"
><!----><!----><input
type="checkbox"
role="switch"
aria-describedby="s6-2"
/><span class="ui-label"><!--[-->Small<!--]--></span
><!----></label
><label class="ui-switch"
><!----><!----><input
type="checkbox"
role="switch"
aria-describedby="s6-3"
/><span class="ui-label"><!--[-->Default<!--]--></span
><!----></label
>
</div>
<!--]-->

Icons

<script setup lang="ts">
import { Switch } from "opui-css/vue"
</script>
<template>
<Switch small aria-label="Toggle theme">
<template #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
></template>
<template #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
></template>
</Switch>
<Switch checked aria-label="Toggle theme">
<template #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
></template>
<template #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
></template>
</Switch>
</template>
<!--[--><label class="ui-switch ui-small"
><span class="ui-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="ui-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"
aria-describedby="s7-0"
/><!----><!----></label
><label class="ui-switch"
><span class="ui-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="ui-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"
aria-describedby="s7-1"
/><!----><!----></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
<script setup lang="ts">
import { FieldGroup, FieldLegend, FieldSet, Form, Switch } from "opui-css/vue"
</script>
<template>
<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>
</template>
<div class="ui-form">
<!--[-->
<fieldset class="ui-fieldset">
<!--[-->
<legend class=""><!--[-->Legend<!--]--></legend>
<div class="ui-field-group" role="group">
<!--[--><label class="ui-switch"
><!----><!----><input
type="checkbox"
role="switch"
aria-describedby="s8-0"
/><span class="ui-label"><!--[-->Switch 1<!--]--></span
><!----></label
><label class="ui-switch"
><!----><!----><input
type="checkbox"
role="switch"
aria-describedby="s8-1"
/><span class="ui-label"><!--[-->Switch 2<!--]--></span
><!----></label
><label class="ui-switch"
><!----><!----><input
type="checkbox"
role="switch"
aria-describedby="s8-2"
/><span class="ui-label"><!--[-->Switch 3<!--]--></span
><!----></label
><!--]-->
</div>
<!--]-->
</fieldset>
<!--]-->
</div>

Direction

Legend
<script setup lang="ts">
import { FieldGroup, FieldLegend, FieldSet, Form, Switch } from "opui-css/vue"
</script>
<template>
<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>
</template>
<form class="ui-form">
<!--[-->
<fieldset class="ui-fieldset">
<!--[-->
<legend class=""><!--[-->Legend<!--]--></legend>
<div class="ui-field-group ui-row" role="group">
<!--[--><label class="ui-switch"
><!----><!----><input
type="checkbox"
role="switch"
aria-describedby="s9-0"
/><span class="ui-label"><!--[-->Switch 1<!--]--></span
><!----></label
><label class="ui-switch"
><!----><!----><input
type="checkbox"
role="switch"
aria-describedby="s9-1"
/><span class="ui-label"><!--[-->Switch 2<!--]--></span
><!----></label
><label class="ui-switch"
><!----><!----><input
type="checkbox"
role="switch"
aria-describedby="s9-2"
/><span class="ui-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

<script setup lang="ts">
import {
FieldDescription,
FieldGroup,
FieldLegend,
FieldSet,
Form,
Switch,
} from "opui-css/vue"
</script>
<template>
<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>
</template>
<form class="ui-form">
<!--[-->
<fieldset class="ui-fieldset">
<!--[-->
<legend class=""><!--[-->Legend<!--]--></legend>
<p class="ui-field-description">
<!--[-->Field description above fields<!--]-->
</p>
<div class="ui-field-group ui-row" role="group">
<!--[--><label class="ui-switch"
><!----><!----><input
type="checkbox"
role="switch"
aria-describedby="s10-0"
/><span class="ui-label"><!--[-->Switch 1<!--]--></span
><!----></label
><label class="ui-switch"
><!----><!----><input
type="checkbox"
role="switch"
aria-describedby="s10-1"
/><span class="ui-label"><!--[-->Switch 2<!--]--></span
><!----></label
><label class="ui-switch"
><!----><!----><input
type="checkbox"
role="switch"
aria-describedby="s10-2"
/><span class="ui-label"><!--[-->Switch 3<!--]--></span
><!----></label
><!--]-->
</div>
<!--]-->
</fieldset>
<fieldset class="ui-fieldset">
<!--[-->
<legend class=""><!--[-->Legend<!--]--></legend>
<div class="ui-field-group ui-row" role="group">
<!--[--><label class="ui-switch"
><!----><!----><input
type="checkbox"
role="switch"
aria-describedby="s10-3"
/><span class="ui-label"><!--[-->Switch 1<!--]--></span
><!----></label
><label class="ui-switch"
><!----><!----><input
type="checkbox"
role="switch"
aria-describedby="s10-4"
/><span class="ui-label"><!--[-->Switch 2<!--]--></span
><!----></label
><label class="ui-switch"
><!----><!----><input
type="checkbox"
role="switch"
aria-describedby="s10-5"
/><span class="ui-label"><!--[-->Switch 3<!--]--></span
><!----></label
><!--]-->
</div>
<p class="ui-field-description">
<!--[-->Field description below fields<!--]-->
</p>
<!--]-->
</fieldset>
<!--]-->
</form>

Disabled

Attach the disabled attribute to the <fieldset> element.

Legend
<script setup lang="ts">
import { FieldGroup, FieldLegend, FieldSet, Form, Switch } from "opui-css/vue"
</script>
<template>
<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>
</template>
<form class="ui-form">
<!--[-->
<fieldset class="ui-fieldset" disabled>
<!--[-->
<legend class=""><!--[-->Legend<!--]--></legend>
<div class="ui-field-group ui-row" role="group">
<!--[--><label class="ui-switch"
><!----><!----><input
type="checkbox"
role="switch"
aria-describedby="s11-0"
/><span class="ui-label"><!--[-->Switch 1<!--]--></span
><!----></label
><label class="ui-switch"
><!----><!----><input
type="checkbox"
role="switch"
aria-describedby="s11-1"
/><span class="ui-label"><!--[-->Switch 2<!--]--></span
><!----></label
><label class="ui-switch"
><!----><!----><input
type="checkbox"
role="switch"
aria-describedby="s11-2"
/><span class="ui-label"><!--[-->Switch 3<!--]--></span
><!----></label
><!--]-->
</div>
<!--]-->
</fieldset>
<!--]-->
</form>

Required

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

These are required!
<script setup lang="ts">
import { FieldGroup, FieldLegend, FieldSet, Form, Switch } from "opui-css/vue"
</script>
<template>
<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>
</template>
<form class="ui-form">
<!--[-->
<fieldset class="ui-fieldset">
<!--[-->
<legend class=""><!--[-->These are required!<!--]--></legend>
<div class="ui-field-group ui-row" role="group">
<!--[--><label class="ui-switch"
><!----><!----><input
type="checkbox"
role="switch"
required
aria-describedby="s12-0"
/><span class="ui-label"><!--[-->Switch 1<!--]--></span
><!----></label
><label class="ui-switch"
><!----><!----><input
type="checkbox"
role="switch"
required
aria-describedby="s12-1"
/><span class="ui-label"><!--[-->Switch 2<!--]--></span
><!----></label
><label class="ui-switch"
><!----><!----><input
type="checkbox"
role="switch"
required
aria-describedby="s12-2"
/><span class="ui-label"><!--[-->Switch 3<!--]--></span
><!----></label
><!--]-->
</div>
<!--]-->
</fieldset>
<!--]-->
</form>

Validation

Attach the data-invalid attribute to your Fieldset component.

Legend
Something went wrong!
<script setup lang="ts">
import { FieldGroup, FieldLegend, FieldSet, Form, Switch } from "opui-css/vue"
</script>
<template>
<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="ui-end-text">Something went wrong!</span>
</FieldSet>
</Form>
</template>
<form class="ui-form">
<!--[-->
<fieldset class="ui-fieldset" data-invalid>
<!--[-->
<legend class=""><!--[-->Legend<!--]--></legend>
<div class="ui-field-group ui-row" role="group">
<!--[--><label class="ui-switch"
><!----><!----><input
type="checkbox"
role="switch"
aria-describedby="s13-0"
/><span class="ui-label"><!--[-->Switch 1<!--]--></span
><!----></label
><label class="ui-switch"
><!----><!----><input
type="checkbox"
role="switch"
aria-describedby="s13-1"
/><span class="ui-label"><!--[-->Switch 2<!--]--></span
><!----></label
><label class="ui-switch"
><!----><!----><input
type="checkbox"
role="switch"
aria-describedby="s13-2"
/><span class="ui-label"><!--[-->Switch 3<!--]--></span
><!----></label
><!--]-->
</div>
<span class="ui-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 .ui-label child for a visible label, or a .ui-sr-only child to hide it visually while keeping it accessible. In Astro, set the hideLabel prop to render the slot content as .ui-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): & .ui-label
  4. End text (optional): .ui-end-text

API

Switch API

Field group API

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(.ui-switch) {
--_motion: var(--motion, 1);
--_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: calc(0.2s * var(--_motion));
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 */
/* 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(.ui-icon-checked, .ui-icon-unchecked) {
[role="switch"] {
grid-column: 1 / -1;
grid-row: 1;
}
&:has([role="switch"]:checked) {
.ui-icon-unchecked {
display: none;
}
.ui-icon-checked {
display: block;
}
}
.ui-icon-checked {
display: none;
}
.ui-icon-unchecked,
.ui-icon-checked {
grid-column: 1;
grid-row: 1;
pointer-events: none;
z-index: 1;
svg {
display: block;
inline-size: var(--_dot-size);
}
}
.ui-icon-unchecked {
margin-inline-start: calc(
var(--_track-width) - var(--_dot-size) - var(--size-1)
);
}
.ui-icon-checked {
margin-inline-start: var(--size-1);
}
}
/* Required dot */
&:has(:invalid) {
.ui-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 */
.ui-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(.ui-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 */
&.ui-small {
--_dot-size: 0.75rem;
--_track-height: var(--size-4);
--_track-width: 2.5rem;
}
/* Spread layout */
&.ui-spread {
align-items: center;
column-gap: var(--size-4);
grid-auto-flow: unset;
grid-template-columns: 1fr auto;
inline-size: 100%;
.ui-label {
font-weight: 600;
grid-column: 1;
grid-row: 1;
inline-size: fit-content;
}
input[role="switch"] {
grid-column: 2;
grid-row: 1;
}
&:has(.ui-icon-checked, .ui-icon-unchecked) {
input[role="switch"],
.ui-icon-checked,
.ui-icon-unchecked {
grid-column: 2;
}
}
:where(.ui-end-text) {
grid-column: 1;
grid-row: 2;
}
}
/* Stacked layout */
&.ui-stack {
grid-auto-columns: unset;
justify-items: center;
.ui-label {
grid-column: 1/-1;
grid-row: 2;
margin-block-start: var(--size-1);
padding-inline: 1ex;
}
.ui-end-text {
grid-column: 1/-1;
grid-row: 3;
}
}
/* Validation */
&[data-invalid] {
input {
border-radius: var(--radius-round);
outline: 2px solid var(--color-9);
}
.ui-end-text {
color: var(--color-9);
}
}
}
}
@layer components.extended {
/* Form */
:where(.ui-form) {
display: grid;
gap: var(--size-8);
hr {
margin-block: 0;
}
}
/* Fieldset */
:where(.ui-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, .ui-legend) {
all: unset;
color: var(--text-primary);
font-weight: 600;
margin-block-end: var(--size-3);
padding: 0;
&:has(+ :where(.ui-field-description)) {
margin-block-end: 0;
}
}
/* Field description / supporting text */
:where(.ui-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(.ui-end-text) {
color: var(--text-muted);
font-size: var(--font-size-0);
line-height: var(--font-lineheight-3);
}
&:has(.ui-text-field.ui-row) {
row-gap: var(--size-7);
}
/* Disabled */
&[disabled] {
opacity: 0.64;
user-select: none;
input,
label,
.ui-label {
cursor: not-allowed;
}
}
/* Error / validation */
&[data-invalid] {
:where(.ui-end-text) {
color: var(--critical);
}
:where(.ui-checkbox, .ui-radio, .ui-switch) {
--primary: var(--critical);
:where(.ui-end-text) {
color: var(--critical);
}
}
:where(.ui-switch) {
input {
border-radius: var(--radius-round);
outline: 2px solid var(--critical);
}
}
}
/* Required */
&:has(:invalid) {
:where(legend, .ui-legend) {
padding-inline-end: 1ex;
position: relative;
&::after {
color: var(--red);
content: "*";
inset: 0 -0.25ex auto auto;
position: absolute;
}
}
}
}
/* Field group */
:where(.ui-field-group) {
display: flex;
flex-direction: column;
gap: var(--size-4);
/* If it only has checkboxes, radios, or switches */
&:has(> :where(.ui-checkbox, .ui-radio, .ui-switch)):not(
:has(> :not(.ui-checkbox, .ui-radio, .ui-switch))
) {
gap: var(--size-2);
}
/* If it only has buttons */
&:has(.ui-button):not(:has(*:not(.ui-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 */
&.ui-row {
flex-direction: row;
flex-wrap: wrap;
&:has(> :where(.ui-checkbox, .ui-radio, .ui-switch)) {
gap: var(--size-4);
}
}
}
}