Components
Switch
See also: Switch field group.
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
requiredattribute on the component. It is forwarded to the underlying<input>. -
Use the
errorprop to toggle invalid styles. It rendersdata-invalidon 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.
<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
<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.
<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.
<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.
<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.
<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
- Container:
labelelement - Switch:
& input type="checkbox" role="switch" - Label (optional): &
.ui-label - End text (optional):
.ui-end-text
API
Switch API
Field group API
Browser support
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); } } }}