# 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`](/components/switch).

::component-preview{name="TogglePreview"}
```vue
<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 `name` and `value` together, or neither, since a `name` without a `value`
  submits the browser’s default of `on`.
- Inside a [`Toggle.Group`](/components/toggle-group), `value` is required and
  the group owns the pressed state.

## Anatomy

Render it on its own.

::component-anatomy
---
parts:
  - name: Toggle.Root
    required: true
    description: Renders a button, plus a visually hidden checkbox input when name is set.
---
::

```vue
<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](/components/composition#controlled-and-uncontrolled).

```vue
<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`](/components/toolbar).

```vue
<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`](/components/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.

```vue
<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](/components/form-integration#form-participation).

```vue
<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.

```vue
<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`](/components/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

::docs-table
---
columns:
  - label: Prop
  - label: Type
  - label: Default
rows:
  - items:
      - label: modelValue
        description: 'The pressed state. `v-model`. A [`Toggle.Group`](#inside-a-toggle-group) owns it instead.'
      - label: 'boolean | undefined'
      - label: undefined
  - items:
      - label: defaultValue
        description: Initial state when [uncontrolled](#controlled-and-uncontrolled).
      - label: boolean
      - label: 'false'
  - items:
      - label: value
        description: Submitted while pressed. Required inside a `Toggle.Group`.
      - label: string
      - label: undefined
  - items:
      - label: name
        description: 'Form field name; renders [the hidden input](#inside-a-form).'
      - label: string
      - label: undefined
  - items:
      - label: form
        description: The `id` of the form, when rendered outside it.
      - label: string
      - label: undefined
  - items:
      - label: disabled
        description: Blocks activation.
      - label: boolean
      - label: 'false'
---
::

#### Emits

::docs-table
---
columns:
  - label: Emit
  - label: Payload
rows:
  - items:
      - label: update:modelValue
        description: The pressed state changes, controlled or not.
      - label: boolean
---
::

#### Slot props

::docs-table
---
columns:
  - label: Prop
  - label: Type
rows:
  - items:
      - label: pressed
        description: Mirrors `data-pressed`.
      - label: boolean
  - items:
      - label: disabled
        description: Mirrors `data-disabled`.
      - label: boolean
---
::

#### Data attributes

::data-attributes
::

#### CSS variables

::css-variables
::

#### Errors

::docs-table
---
columns:
  - label: Code
rows:
  - items:
      - label: missing_item_value
        description: A `Toggle.Root` inside a `Toggle.Group` was rendered without a `value`.
---
::

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.

::docs-table
---
columns:
  - label: Key
  - label: Action
rows:
  - items:
      - label: Enter
      - label: Toggles, on `keydown`.
        plaintext: true
  - items:
      - label: Space
      - label: Toggles, on `keyup`. The `keydown` default is prevented.
        plaintext: true
---
::
