# Accessibility

Read this to see which accessibility work the components do for you, which parts stay your app’s job, and how the test suite verifies both.

Roles, keyboard maps and focus behaviour are the same in every product, so
Mirror ships them and there is no prop to switch them off.

## What Mirror guarantees

### Roles and ARIA state

Every part renders the role its pattern requires and keeps the matching state
attributes in sync. `Checkbox` is `role="checkbox"` with `aria-checked` as
`true`, `false` or `mixed`; `Tabs` is a tablist of tabs controlling panels; and
`Select` is a button with `aria-haspopup="listbox"` driving a listbox.

### Relationships

`Field` wires the label, the description and the error to the control.
`Field.Label` gets `for`, the control gets `aria-describedby` listing
descriptions then errors, and `Field.Error` renders as `role="alert"`.

```vue
<template>
  <Field.Root :options="{ name: 'email' }">
    <Field.Label>Email</Field.Label>
    <Input v-model="email" type="email" />
    <Field.Error>Enter an email address.</Field.Error>
  </Field.Root>
</template>
```

### Keyboard maps

Each interactive component implements the keyboard map from its ARIA pattern,
including the parts that are easy to miss: roving focus and one tab stop for
`Radio` and `Tabs`, `PageUp` and `PageDown` on `Slider`, typeahead on `Select`,
and Enter blocked on `Checkbox` so the form owns it. The map is tabulated on
every component page.

### Focus management

Popups trap focus while modal and return it to the trigger on close. A disabled
non-native control keeps its `tabindex` and reports `aria-disabled` rather than
leaving the tab order, so a screen-reader user can still reach it and hear that
it is disabled.

### The focus ring

Both the colour and the width of `focus-ring` come from tokens rather than from
a utility, so an app cannot style the ring down to nothing while still using
the class.

## What stays your job

### Accessible names

Mirror never invents one, so a `Toggle.Root` whose content is a glyph, a
`Progress.Root`, and any control outside a `Field` all need an `aria-label` or
an `aria-labelledby` from you.

```vue
<template>
  <Toggle.Root aria-label="Bold" v-model="bold">
    <icon-bold />
  </Toggle.Root>
</template>
```

### Colour contrast

The `on-*` colours are paired with their fills in the token tree, so text on a
fill stays legible. Any other pairing is yours to check, and so is every colour
your app defines outside the tree.

### Visible focus

The parts report focus state but draw nothing, so apply
`focus-visible:focus-ring` to anything a user can reach.

### Motion preferences

Timing lives in your CSS, so honouring `prefers-reduced-motion` is a media
query you write.

```css
@media (prefers-reduced-motion: reduce) {
  .mirror-select-popup-enter-active,
  .mirror-select-popup-leave-active {
    transition: none;
  }
}
```

### Reading order

`asChild` and `as` let you render anything, so keep the DOM order the reading
order and keep a control’s label adjacent to it.

## How it is verified

Every component in `packages/vue` has a dedicated ARIA test file and, where it
takes keyboard input, a dedicated keyboard test file. They assert the roles,
the state attributes and the key handling described on that component’s page,
so the docs and the suite move together.

```
packages/vue/src/components/Checkbox/tests/
  Checkbox.aria.test.ts
  Checkbox.keyboard.test.ts
```

Errors are branded and thrown rather than swallowed, so a part assembled in a
way that would break its ARIA relationships fails loudly at render.

## Further reading

- [Form integration](/components/form-integration): `Field`, validation and
  native constraint validation.
- [Animation](/components/animation): where timing lives, and how to switch it
  off.
