Toggle
A button that stays pressed, for a formatting control or a filter chip.
Toggle is a button that stays pressed once activated, and reports that state as
aria-pressed. Useful for a formatting control or a filter chip; for a setting
the user turns on and off, use Switch.
<template>
<div class="flex items-center gap-1" role="group" aria-label="Formatting">
<Toggle.Root
v-for="format in formats"
:key="format.label"
v-model="format.pressed"
:aria-label="format.label"
:class="[style, format.glyphClass]"
>
{{ format.glyph }}
</Toggle.Root>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { Toggle } from '@maas/mirror/vue'
const style = [
'inline-flex size-10 items-center justify-center',
'rounded-component-md border-2 border-[transparent] type-component-md',
'text-primary-solid transition-all duration-100 ease-linear',
'outline-4 outline-transparent focus-visible:focus-ring',
'hover:bg-primary-subtle',
'active:bg-primary-subtle-active',
'data-[state=on]:bg-primary-solid data-[state=on]:text-primary-on-solid',
'data-[state=on]:hover:bg-primary-solid-hover',
'data-[state=on]:active:bg-primary-solid-active',
].join(' ')
const formats = ref([
{ label: 'Bold', glyph: 'B', glyphClass: 'font-bold', pressed: true },
{ label: 'Italic', glyph: 'I', glyphClass: 'italic', pressed: false },
{ label: 'Underline', glyph: 'U', glyphClass: 'underline', pressed: false },
])
</script>
Usage guidelines
- A toggle whose content is a glyph has no accessible name, so give it an
aria-label, as every example on this page does. - Set
nameandvaluetogether, or neither, since anamewithout avaluesubmits the browser’s default ofon. - Inside a
Toggle.Group,valueis required and the group owns the pressed state.
Anatomy
Render it on its own.
| Name | Required | Description |
|---|---|---|
| Toggle.Root | true | Renders a button, plus a visually hidden checkbox input when name is set. |
<script setup lang="ts">
import { Toggle } from '@maas/mirror/vue'
</script>
<template>
<Toggle.Root v-model="bold" name="format" value="bold" aria-label="Bold">
B
</Toggle.Root>
</template>
Examples
Controlled and uncontrolled
Leave modelValue out and the toggle keeps its own state, seeded by
defaultValue. Bind v-model and you own it instead, which is
decided once at mount.
<template>
<Toggle.Root default-value aria-label="Bold">B</Toggle.Root>
<Toggle.Root v-model="italic" aria-label="Italic">I</Toggle.Root>
</template>
<script setup lang="ts">
const italic = ref(false)
</script>
A row of toggles
A formatting row is a set of independent toggles, each with its own state,
so the container around them is yours to write. Where the row needs one tab
stop and arrow keys, reach for Toolbar.
<template>
<div role="group" aria-label="Text formatting">
<Toggle.Root
v-for="format in formats"
:key="format.name"
v-model="format.pressed"
:aria-label="format.name"
>
{{ format.glyph }}
</Toggle.Root>
</div>
</template>
<script setup lang="ts">
const formats = ref([
{ name: 'Bold', glyph: 'B', pressed: false },
{ name: 'Italic', glyph: 'I', pressed: false },
])
</script>
Inside a Toggle Group
Nested in a Toggle.Group, a toggle stops owning
its own state: the group holds the value, and value becomes how the group
tells one toggle from another. It is required there, and a toggle without one
throws missing_item_value. modelValue and defaultValue are ignored in a
group, so bind v-model to the group instead.
<template>
<Toggle.Group v-model="alignment">
<Toggle.Root value="left">Left</Toggle.Root>
<Toggle.Root value="centre">Centre</Toggle.Root>
<Toggle.Root value="right">Right</Toggle.Root>
</Toggle.Group>
</template>
Inside a form
Set name and Toggle.Root renders a visually hidden checkbox input, checked
while the toggle is on, so
the form sees it.
<template>
<form>
<Toggle.Root v-model="bold" name="format" value="bold" aria-label="Bold">
B
</Toggle.Root>
</form>
</template>
A toggle rendered outside the <form> it belongs to points at it by id
through form.
Styling from state
Paint the pressed state from data-state, which is on the element in both
states.
<template>
<Toggle.Root class="toggle" aria-label="Bold">B</Toggle.Root>
</template>
<style>
.toggle[data-state='on'] {
background: var(--app-color-primary-bg-solid);
color: var(--app-color-primary-fg-on-solid);
}
.toggle[data-disabled='true'] {
background: var(--app-color-disabled-bg-subtle);
}
</style>
API reference
Module. Toggle.Root takes ordinary props and keeps no store of its own;
nested in a Toggle.Group it reads the group’s.
Toggle.Root takes no part in a Field: it has no readOnly and no
required, and inherits nothing from a surrounding Field.Root.
Toggle.Root
Renders a <button>, plus a visually hidden <input type="checkbox"> once
name is set.
Props
Emits
Slot props
Data attributes
CSS variables
Errors
On its own, Toggle.Root needs no context and no required prop, so nothing
throws.
Accessibility
The rendered element is a <button> with aria-pressed, and a non-native
toggle gets role="button", tabindex="0" and aria-disabled rather than
leaving the tab order.