Quasar QTable Accessibility
Permalink to "Quasar QTable Accessibility"Quasar’s QTable renders a semantic table with sorting, filtering, selection, pagination, virtual scrolling and a “grid mode” that displays rows as cards on small screens. It is a capable base for Vue apps built with Quasar, across web, mobile and desktop targets. The accessibility review focuses on four areas: whether header sorting is reachable by keyboard and exposes state, how selection and pagination controls are named, what grid mode does to table semantics, and the announcements the component does not make.
This guide belongs to Vue & Nuxt data components.
Spec reference
Permalink to "Spec reference"- Semantic table —
tablemarkup in standard mode. - Sortable headers — verify that headers are focusable, sortable with Enter, and expose
aria-sortin your version; add it via the header slot if not. - Grid mode — renders cards, not table rows; table semantics are lost, so each card needs its own structure (heading, labelled values).
- SC 1.3.1, 4.1.2 (A); SC 4.1.3, 2.4.7 (AA).
When to use — and when not to
Permalink to "When to use — and when not to"Use QTable for Quasar apps; use table mode on wide screens and grid mode on narrow ones if cards are well structured. Use virtual scroll for large client-side datasets, with care for screen reader access.
Do not switch to grid mode without restructuring card content; a card of bare values (“INV-1042”, “Lund & Co”, “1,240.00”) has lost the meaning headers provided. The misapplication: relying on header clicks for sorting when headers are not keyboard focusable in your configuration.
Annotated code example
Permalink to "Annotated code example"<template>
<q-table
title="Invoices" :rows="rows" :columns="columns" row-key="id"
selection="multiple" v-model:selected="selected"
:filter="filter" :grid="$q.screen.lt.md"
:pagination="pagination" @request="onRequest"
:table-attrs="{ 'aria-labelledby': 'inv-title' }" <!-- name on the table element (SC 4.1.2) -->
>
<template #top-left><h2 id="inv-title" class="text-h6">Invoices</h2></template>
<template #top-right>
<q-input v-model="filter" label="Filter invoices" dense debounce="400" /> <!-- labelled -->
</template>
<!-- Header with explicit sort semantics if needed (SC 4.1.2) -->
<template #header-cell="props">
<q-th :props="props" :aria-sort="ariaSort(props.col)">{{ props.col.label }}</q-th>
</template>
<!-- Per-row checkbox label -->
<template #body-selection="scope">
<q-checkbox v-model="scope.selected" :aria-label="`Select invoice ${scope.row.id}`" />
</template>
<!-- Grid mode cards with structure -->
<template #item="props">
<article class="invoice-card" :aria-labelledby="`card-${props.row.id}`">
<h3 :id="`card-${props.row.id}`">{{ props.row.id }}</h3>
<dl><dt>Customer</dt><dd>{{ props.row.customer }}</dd><dt>Amount</dt><dd>{{ props.row.amount }}</dd></dl>
</article>
</template>
</q-table>
<p role="status" class="visually-hidden">{{ status }}</p>
</template>
table-attrs and slot names follow Quasar 2; verify against your version.
Keyboard & AT behaviour
Permalink to "Keyboard & AT behaviour"| Interaction | Expected | Notes |
|---|---|---|
| T (table navigation) | “Invoices, table…” | Table mode only |
| Tab to header | Focus on sortable header | Verify focusability |
| Enter on header | Sort and announce | Status message added by you |
| H in grid mode | Moves between cards | Card headings provide structure |
| Filter typing | Results update | “12 invoices match” status |
Integration context
Permalink to "Integration context"Card layouts for small screens are discussed in stacked card layouts that keep table semantics. Virtual scrolling trade-offs are covered in accessible virtual scrolling in Vue with vue-virtual-scroller. Announcements can use a Vue announcer composable for data updates.
Gotchas
Permalink to "Gotchas"Mobile apps (Capacitor, Cordova). On native wrappers, test with VoiceOver on iOS and TalkBack on Android; web views follow browser accessibility, but focus and scrolling may differ.
Dense mode. dense reduces padding and target sizes; check checkboxes and buttons against 24 px.
Virtual scroll. Only rendered rows exist; screen reader users may lose orientation. Prefer pagination where possible, or ensure row positions are conveyed.
Common mistakes
Permalink to "Common mistakes"- Grid mode cards without headings or labels.
- Header sorting reachable only by mouse.
- Unlabelled filter input (placeholder only).
- Silent pagination.
Version and upgrade notes
Permalink to "Version and upgrade notes"Quasar 2 (Vue 3) changed slot syntax and some props compared with Quasar 1. When upgrading, re-check header slots, table-attrs and selection slots, and re-run keyboard tests on sorting and pagination.
Worked example
Permalink to "Worked example"A field-service app used QTable with grid mode on phones. Technicians using VoiceOver heard a stream of unlabelled values on each card. The team turned cards into articles with the job number as a heading and a description list for values, added a “Sort by” select above the grid, and reused the same status region for result counts. In table mode, they added aria-sort through the header slot and labelled per-row checkboxes. VoiceOver users could now jump between jobs with the heading rotor.
Adoption checklist for teams
Permalink to "Adoption checklist for teams"- Wrap QTable with props for the table name and a card template for grid mode.
- Add
aria-sortthrough the header-cell slot, computed from pagination sort state. - Label row selection checkboxes and the select-all checkbox.
- Label the filter input with a visible label, not only a placeholder.
- Wire
@request(server mode) or filter and sort changes to the shared announcer. - Test both modes: table on desktop, grid on a phone with VoiceOver and TalkBack.
Teams that ship grid mode without a card template are the most common source of Quasar table accessibility bugs; making the template mandatory in the wrapper prevents it.
Testing checklist
Permalink to "Testing checklist"Design system notes
Permalink to "Design system notes"Provide a QTable wrapper that handles naming, header sort semantics, selection labels, a structured card template for grid mode and announcements. Grid mode without a card template should be disallowed.
FAQ
Permalink to "FAQ"Is QTable a semantic table?
In table mode, yes. In grid mode it renders cards, which need their own structure.
How do I add aria-sort?
Through the header-cell slot on q-th, computed from the current pagination sort settings.
Does QTable announce anything?
Not by itself; add a status region.
Is virtual scroll accessible?
It can be, with care; for screen reader-heavy use, pagination is often easier.
How do I name the table?
Use table-attrs to set aria-labelledby on the table element, pointing at the title.
Should mobile always use grid mode?
Not necessarily; a horizontally scrollable table with a sticky first column can work. Choose based on content and test both.
Can dense mode be accessible?
Yes, if interactive targets stay at 24 px and text remains readable; verify after enabling it.
How do I name the select-all checkbox?
Override the header-selection slot and set aria-label to “Select all invoices on this page”, matching the scope of the action.
Does QTable support row expansion?
Yes, through custom slots; give the expand control a name, aria-expanded and place the expanded content right after its row.
Related
Permalink to "Related"- Stacked card layouts — cards that keep meaning
- Vuetify data table — another Vue table
- Vue announcer composable — announcements