Skip to main content
Vuetify0 v1.0 is here
Vuetify0 Logo
Theme
Mode
Palettes
Accessibility
Vuetify One
Sign in to Vuetify One

Access premium tools across the Vuetify ecosystem — Bin, Play, Studio, and more.

Not a subscriber? See what's included

Accessibility

v0 provides ARIA attributes out-of-the-box through the attrs pattern. You provide styling and visual feedback.

Edit this page
Report a Bug
Copy Markdown

IntermediateAug 3, 2026

The attrs Pattern

Every v0 component exposes an attrs object containing all accessibility attributes. Spread it onto your elements:

vue
<script setup>
  import { Selection } from '@vuetify/v0'

  const items = ['Apple', 'Banana', 'Cherry']
</script>

<template>
  <Selection.Root v-slot="{ attrs }">
    <div v-bind="attrs">
      <Selection.Item v-for="item in items" :key="item" v-slot="{ attrs }">
        <button v-bind="attrs">{{ item }}</button>
      </Selection.Item>
    </div>
  </Selection.Root>
</template>

What’s Included in attrs

ComponentARIA Attributes Provided
Selection.Itemaria-selected, aria-disabled, data-selected, data-disabled
Group.Itemrole="checkbox", aria-checked, aria-disabled, data-selected, data-disabled, data-mixed
ExpansionPanel.Activatorid, role, tabindex, aria-expanded, aria-controls, aria-disabled
Pagination.Rootaria-label, role="navigation"[1]
Popover.Activatorpopovertarget, data-open[2]
Tip

Always spread the attrs object from slot props onto your interactive elements. Missing ARIA attributes break screen reader support.

Developer Responsibilities

v0 provides the ARIA plumbing. You must provide:

ResponsibilityExample
Visual focus indicators:focus-visible { outline: 2px solid blue }
Color contrastEnsure 4.5:1 ratio minimum
Visible labelsAdd <label> or aria-label for inputs
Skip linksNavigation landmarks for keyboard users

Focus Trapping

v0 does not provide focus trapping. Use external solutions:

Roving Tabindex

v0 does not provide roving tabindex. This keeps the library headless - implement in your design system layer if needed for arrow key navigation between items.

Teleported Content and Landmarks

Content teleported by Portal renders into body, outside your app’s landmarks, so audit tools flag it with the axe region↗︎ rule. v0 is headless — it won’t pick a landmark role for you. Give the teleported subtree its own semantics: role="dialog" for modals (exempt from the landmark rule), role="status" or role="alert" for toast regions, or role="region" plus aria-label for arbitrary overlays. See the Portal accessibility notes for an example.

Keyboard Navigation

What v0 Handles

  • tabindex management (-1 when disabled, 0 when enabled)

  • ARIA state synchronization (aria-expanded, aria-selected)

  • Data attributes for styling (data-selected, data-disabled)

What You Implement

PatternKeys to Handle
List selectionArrow keys, Home/End
MenusArrow keys, Escape, Enter
DialogsEscape to close, focus trap
TabsArrow keys, Home/End
ts
// You implement navigation logic - v0 provides selection state
function onKeydown (e: KeyboardEvent, ids: string[], currentIndex: number) {
  switch (e.key) {
    case 'ArrowDown': selection.select(ids[currentIndex + 1]); break
    case 'ArrowUp': selection.select(ids[currentIndex - 1]); break
    case 'Home': selection.select(ids[0]); break
    case 'End': selection.select(ids[ids.length - 1]); break
  }
}

Testing Strategies

Automated Testing

MyComponent.test.ts
import { axe } from 'vitest-axe'

it('passes accessibility audit', async () => {
  const { container } = render(MyComponent)
  expect(await axe(container)).toHaveNoViolations()
})

vitest-axe is a Node package — it reaches for node:module on import, so it works in a jsdom or happy-dom test environment and not in Vitest’s browser mode. If your components are tested in a real browser, drop the wrapper and call axe-core directly:

MyComponent.browser.test.ts
import axe from 'axe-core'

it('passes accessibility audit', async () => {
  const { container } = render(MyComponent)
  const results = await axe.run(container)
  expect(results.violations).toEqual([])
})

Prefer the browser form where you can. Layout-dependent rules — colour contrast, target size, element overlap — need real computed styles, and a simulated DOM either skips them or answers from a layout that does not exist. v0 audits its own components this way; see packages/0/src/components/a11y.browser.test.ts.

Manual Testing Checklist

Use this checklist during manual QA:

markdown
- [ ] Tab through all interactive elements
- [ ] Verify focus visibility
- [ ] Test with keyboard only (no mouse)
- [ ] Check color contrast with DevTools
- [ ] Validate with browser accessibility tree
ToolPurpose
axe DevToolsBrowser extension for WCAG scanning
LighthouseBuilt-in Chrome audit
NVDA/VoiceOverScreen reader verification

Internationalization

Every v0 component ships an inline English accessible name for the ARIA labels it renders — a Dialog close button reads "Close", a pagination landmark reads "Pagination", and so on. Each default lives at the component’s call site as locale.ti(key) ?? 'English default', so an app that never installs a locale plugin still satisfies WCAG 4.1.2 Name, Role, Value↗︎ out of the box. No locale strings are bundled into the runtime.

To localize those names, install the Locale plugin with translations for the component keys (Dialog.close, Pagination.label, Carousel.next, …). When a key resolves the component uses your translation; when it doesn’t, the inline English default still renders. The optional @vuetify/v0/locale/messages/en export is the canonical map of every key v0 components look up — import it as a starting point for a new translation, or register it as-is for a complete English baseline.

For RTL (right-to-left) support, see useRtl. Direction is managed independently from locale — useRtl provides a reactive isRtl boolean and sets the dir attribute on the target element.


  1. role="navigation" is only added when the root element isn’t already a <nav>. If you render Pagination.Root as="nav", the role is omitted to avoid redundant landmark roles. ↩︎

  2. Popover.Activator wires the native Popover API↗︎ via the popovertarget attribute. See the Browser Support page for fallback behavior in older browsers. ↩︎

Was this page helpful?

© 2016-1970 Vuetify, LLC
Services
Ctrl+/