Vue & Nuxt Data Components
Permalink to "Vue & Nuxt Data Components"Vue developers building data-heavy apps usually reach for a component library — Vuetify, PrimeVue or Quasar — and, increasingly, Nuxt for routing and rendering. All three libraries render semantic tables by default, which is a good starting point: screen reader users can use their familiar table commands, and headers are announced with cells. The gaps are the same ones every library leaves: names, labels for repeated controls, meaningful empty and error states, announcements when data changes, and route behaviour that makes client-side navigation as clear as a page load.
This topic covers the three libraries and the two pieces of glue that make them work well together: a shared announcer composable and Nuxt route announcements. The users most affected are screen reader users (names, announcements, route changes), keyboard users (sortable headers, focus after navigation), mobile users (card modes) and low-vision users (focus visibility and density). The failure this topic prevents is a Vue app where each table works slightly differently and none of them tells users what just happened.
This topic belongs to accessible data component libraries.
WCAG criteria in scope
Permalink to "WCAG criteria in scope"| Criterion | Level | Relevance in Vue data apps |
|---|---|---|
| 1.3.1 Info and Relationships | A | Table semantics; card structure in grid modes |
| 2.4.2 Page Titled | A | Titles per Nuxt route |
| 2.4.3 Focus Order | A | Focus after navigation and on Back |
| 2.4.7 Focus Visible | AA | Theme focus styles |
| 4.1.2 Name, Role, Value | A | Table names; checkbox and control labels |
| 4.1.3 Status Messages | AA | Sorting, filtering, paging, errors, route changes |
Prerequisites
Permalink to "Prerequisites"- Semantic HTML table construction — what these libraries render.
- Building an accessible sortable table in Vue — the behaviour from first principles.
- Managing route change focus in Angular and Vue — client-side navigation basics.
ARIA & HTML spec reference
Permalink to "ARIA & HTML spec reference"| Element / attribute | Library or you | Notes |
|---|---|---|
table, th, td |
Library | Semantic output in all three |
aria-sort |
Library (verify) | Add via header slots if missing |
Table name (aria-labelledby, caption) |
You | Use pass-through or table attributes |
| Checkbox labels | You (override defaults) | Include the record identifier |
role="status" / role="alert" regions |
You | One pair at the app root |
<title> per route |
You | useHead in Nuxt |
tabindex="-1" on h1 |
You | Focus target on navigation |
Step-by-step implementation
Permalink to "Step-by-step implementation"Step 1 — Choose and verify the library
Permalink to "Step 1 — Choose and verify the library"See Vuetify data table accessibility, PrimeVue DataTable accessibility and Quasar QTable accessibility.
Step 2 — Names and labels (SC 4.1.2)
Permalink to "Step 2 — Names and labels (SC 4.1.2)"<DataTable :pt="{ table: { 'aria-labelledby': 'inv-h' } }" />
<v-checkbox-btn :aria-label="`Select invoice ${item.id}`" />
Step 3 — One announcer (SC 4.1.3)
Permalink to "Step 3 — One announcer (SC 4.1.3)"const { announce } = useAnnouncer(); announce('42 invoices shown', { key: 'count' });
See a Vue announcer composable for data updates.
Step 4 — Route announcements (SC 2.4.2, 2.4.3)
Permalink to "Step 4 — Route announcements (SC 2.4.2, 2.4.3)"<NuxtRouteAnnouncer />
See Nuxt route announcements for data pages.
Step 5 — Structured mobile modes (SC 1.3.1)
Permalink to "Step 5 — Structured mobile modes (SC 1.3.1)"<article :aria-labelledby="`card-${row.id}`"><h3 :id="`card-${row.id}`">{{ row.id }}</h3><dl>…</dl></article>
Step 6 — Upgrade testing
Permalink to "Step 6 — Upgrade testing"Run keyboard and screen reader smoke tests and ARIA snapshots on every library upgrade branch.
Keyboard interaction contract
Permalink to "Keyboard interaction contract"| Context | Key | Expected | Failure indicator |
|---|---|---|---|
| Table header | Tab, Enter | Sort and announce | Mouse-only sorting |
| Row checkbox | Space | Toggle; count announced | Generic labels |
| Pagination | Tab, Enter | Page change announced; focus stays | Silent paging |
| Filter input | Typing | Results announced after pause | Count per keystroke |
| Link to record | Enter | Navigate; title announced; focus on h1 | Silence |
| Back | Browser Back | List restored with focus | Top of page |
Screen reader compatibility matrix
Permalink to "Screen reader compatibility matrix"| Library / feature | NVDA + Firefox | JAWS + Chrome | VoiceOver + Safari | Notes |
|---|---|---|---|---|
| Vuetify table | Good | Good | Good | Name and labels needed |
| PrimeVue DataTable | Good | Good | Good | Translate aria strings |
| Quasar QTable (table mode) | Good | Good | Good | Verify header sorting |
| Quasar grid mode | Depends on cards | Depends on cards | Depends on cards | Headings and dl required |
| NuxtRouteAnnouncer | Announced | Announced | Announced | Titles must be unique |
Edge cases & failure modes
Permalink to "Edge cases & failure modes"1. Attributes not reaching the table
Permalink to "1. Attributes not reaching the table"Diagnosis: aria-labelledby lands on a wrapper. Fix: pass-through or table-attrs props.
2. Default English strings
Permalink to "2. Default English strings"Diagnosis: “Select row” in every language. Fix: locale configuration.
3. Regions in components
Permalink to "3. Regions in components"Diagnosis: silent announcements after re-renders. Fix: root announcer.
4. Focus jumps on filter
Permalink to "4. Focus jumps on filter"Diagnosis: global afterEach focuses h1. Fix: path-only focus.
5. Unstructured cards
Permalink to "5. Unstructured cards"Diagnosis: grid mode loses headers. Fix: headings and description lists.
Vuetify data table accessibility
Permalink to "Vuetify data table accessibility"Semantic tables with slots; add names, per-row labels, meaningful slots and announcements. See Vuetify data table accessibility.
<template #no-data>No invoices match: {{ filterSummary }}.</template>
PrimeVue DataTable accessibility
Permalink to "PrimeVue DataTable accessibility"Documented accessibility; configure pass-through names, aria locale strings, templates and announcements. See PrimeVue DataTable accessibility.
locale: { aria: { selectAll: 'Select all invoices' } }
Quasar QTable accessibility
Permalink to "Quasar QTable accessibility"Table and grid modes; verify header sorting, label controls and structure cards. See Quasar QTable accessibility.
<q-th :props="props" :aria-sort="ariaSort(props.col)">{{ props.col.label }}</q-th>
A Vue announcer composable for data updates
Permalink to "A Vue announcer composable for data updates"One root polite region and one assertive region, a keyed queue and clearing for repeats. See a Vue announcer composable for data updates.
queue.set(key, { message, politeness });
Nuxt route announcements for data pages
Permalink to "Nuxt route announcements for data pages"Route announcer and heading focus for path changes; data announcer for query changes. See Nuxt route announcements for data pages.
if (to.path === from.path) return; // query-only: no focus move
Cross-cutting concerns
Permalink to "Cross-cutting concerns"SSR and hydration. Render live regions in the root layout so they exist on first paint, and avoid announcing during hydration; announce only on user-driven changes.
Internationalisation. Library aria strings, your messages and page titles all need translation. Use Vue I18n for your messages and the libraries’ locale options for theirs.
Theming. All three libraries offer theming that can reduce focus visibility. Verify focus and selection contrast in light and dark themes with token tests.
Consistency. If a product uses two libraries (common after acquisitions), give both the same wrapper conventions and announcement wording.
Worked example: a Nuxt admin with three tables
Permalink to "Worked example: a Nuxt admin with three tables"An admin app built with Nuxt and Vuetify had three tables (users, invoices, audit log), each with its own aria-live div and no names. Navigation between pages was silent. The team added NuxtRouteAnnouncer, unique titles per page and heading focus on path changes; replaced the three live regions with one announcer composable; named each table from its page heading; overrode checkbox labels; and wrote meaningful empty and error slots. A screen reader user who had been relying on a colleague to “check the admin” could now do the weekly user review alone.
Who is affected, and how
Permalink to "Who is affected, and how"| User group | Typical failure | Fix |
|---|---|---|
| Screen reader users | Silent navigation and updates | Route announcer; data announcer |
| Keyboard users | Focus lost after navigation | Heading focus; Back restoration |
| Mobile users | Unstructured cards | Headings and labelled values |
| Low-vision users | Theme focus styles | Token tests |
| Non-English users | English aria strings | Locale configuration |
Choosing between Vue libraries
Permalink to "Choosing between Vue libraries"The three libraries differ less in accessibility than in ecosystem and features. Vuetify fits Material-style apps and has well-structured slots for overriding labels and states. PrimeVue has the broadest feature set and the most explicit accessibility documentation, plus a pass-through API that makes targeting inner elements straightforward. Quasar suits teams building for web, mobile and desktop from one codebase and offers grid mode for small screens. For all three, the deciding questions are practical: can you name the table element, override per-row labels, provide meaningful states and hook into sort, filter and page events? If yes, the library can be made accessible with a wrapper.
Evaluating a library before adoption
Permalink to "Evaluating a library before adoption"Spend a few hours per candidate: render a table with realistic data, test sortable headers by keyboard, inspect the accessibility tree for names and aria-sort, check checkbox labels, page with a screen reader, switch to mobile layouts, and toggle dark mode. Review the library’s issue tracker for open accessibility issues and response times. Write down what you would need to add in a wrapper; the shorter that list, the better the fit.
Design system integration
Permalink to "Design system integration"Provide a Vue package with wrappers for your chosen table library, the announcer composable, a Nuxt layer with route announcements and focus handling, and theme tokens. Wrappers should require a table name, supply labels from item-value or dataKey, provide standard slots and wire the announcer. Document which library features are approved and which are discouraged (for example, menu filters on every column).
Common mistakes in Vue data apps
Permalink to "Common mistakes in Vue data apps"- Regions in components that re-render or unmount, so announcements are lost.
- Library defaults left untouched — names, aria strings and empty texts.
- Global focus on every route change, including query-only changes.
- Grid or card modes with bare values.
- No tests after upgrades, despite major API changes between library versions.
Measuring quality
Permalink to "Measuring quality"For each data page, record: table named (yes/no), labelled controls (yes/no), announcements for sort, filter and page (yes/no), route announcement and focus on navigation (yes/no), and last screen reader test. The resulting checklist across pages shows coverage and makes progress visible.
A short audit script for Vue data pages
Permalink to "A short audit script for Vue data pages"Open the page with NVDA or VoiceOver. Press T to reach the table and listen for its name. Tab to a sortable header, press Enter, and listen for the sort state and a result message. Tab to a row checkbox and listen for the record identifier. Change a filter and wait for a count. Page forward and listen for the page number. Activate a record link and listen for the page title; press Back and check where focus lands. Ten minutes per page catches nearly every gap described in this topic.
Testing checklist
Permalink to "Testing checklist"Automated
Permalink to "Automated"Keyboard
Permalink to "Keyboard"Screen reader
Permalink to "Screen reader"FAQ
Permalink to "FAQ"Which Vue table library is most accessible?
All three render semantic tables. PrimeVue documents accessibility most explicitly; Vuetify and Quasar are solid with configuration. Choose by ecosystem and test.
Do I need a grid instead of a table?
Only for cell-level interaction (spreadsheets, inline editing across cells). Most Vue admin tables work best as semantic tables.
Should I use vue-announcer or my own composable?
Either; the requirements are the same: root regions, queue and clearing.
How do I handle Nuxt pages with heavy data loading?
Announce loading only if it is slow, then announce results; avoid announcing during hydration.
How do I test Vue components for announcements?
Unit test the announcer’s state after component actions; use speech logs for end-to-end checks.
What about Vue 2 apps?
The principles are the same; library APIs differ. Migrate to Vue 3 libraries when possible, as many Vue 2 libraries are no longer maintained.
Should tables have captions?
A caption or a referenced heading gives the table its name. Use whichever fits your layout.
How do I keep three libraries consistent?
Wrappers with the same props, strings and announcement keys.
Does Nuxt server rendering help accessibility?
It helps by delivering real HTML quickly, including table structure, before JavaScript loads. Make sure hydration does not replace focused elements or recreate live regions.
What about Vue Native or mobile frameworks?
For Capacitor or Cordova apps, the web view follows browser accessibility; for native frameworks, use platform accessibility APIs instead of ARIA.
How do we migrate from Vue 2 tables?
Treat the migration as a rebuild: start from a wrapper with the accessibility defaults described here, then port columns and features, and test with screen readers before switching users over.
Who maintains the wrappers?
The design system or platform team, with product teams contributing features. Wrappers without owners decay into copies.
Related
Permalink to "Related"- React data grid libraries — the React equivalents
- Angular data components — the Angular equivalents
- Screen reader announcement strategies — message design
- Responsive data tables — small-screen layouts
- Focus management in single-page apps — navigation focus