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".
<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.
<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.
@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:
Field, validation and native constraint validation. - Animation: where timing lives, and how to switch it off.