Angular Data Components
Permalink to "Angular Data Components"Angular has an unusually strong accessibility toolkit built in. The CDK’s a11y package provides a shared live announcer, focus origin tracking, focus traps and key managers for roving focus; Angular Material builds on it with a semantic table, accessible sort headers and labelled paginators; PrimeNG adds a feature-rich table with documented accessibility. With signals, data state and the text that describes it become easy to derive. The pieces are there — what teams often miss is putting them together consistently: one announcer, gated announcements, native table markup, row headers, and focus restored after every overlay.
This topic covers that assembly. The users most affected are screen reader users (silent sorts, unnamed tables, lost announcements), keyboard users (focus after dialogs, roving focus in custom lists), and low-vision users (focus visibility in Material themes and density). The failure this topic prevents is an Angular app that has the CDK installed but still hand-rolls live regions and focus logic in every component.
This topic belongs to accessible data component libraries.
WCAG criteria in scope
Permalink to "WCAG criteria in scope"| Criterion | Level | Angular tool that helps |
|---|---|---|
| 1.3.1 Info and Relationships | A | Native mat-table and p-table markup |
| 2.1.1 Keyboard | A | mat-sort-header, ListKeyManager |
| 2.1.2 No Keyboard Trap | A | CDK Dialog and FocusTrap |
| 2.4.3 Focus Order | A | Dialog focus restore, FocusMonitor |
| 2.4.7 Focus Visible | AA | FocusMonitor classes, :focus-visible |
| 4.1.2 Name, Role, Value | A | aria-sort, labelled paginator |
| 4.1.3 Status Messages | AA | LiveAnnouncer |
Prerequisites
Permalink to "Prerequisites"- Building an accessible sortable table in Angular — the pattern from first principles.
- Managing route change focus in Angular and Vue — Angular Router focus.
- Angular CDK virtual scroll accessibility — virtualized lists.
ARIA & HTML spec reference
Permalink to "ARIA & HTML spec reference"| Feature | Provided by | You add |
|---|---|---|
| Table semantics | Native table mat-table, p-table |
Row headers (th scope="row") |
aria-sort |
mat-sort-header, pSortableColumn |
— |
| Sort description | sortActionDescription |
Per-column text |
| Live region | LiveAnnouncer |
Messages, gating, merging |
| Focus origin | FocusMonitor |
Restore with origin |
| Focus trap | CDK Dialog, cdkTrapFocus |
Correct use only for modals |
| Roving focus | FocusKeyManager, ActiveDescendantKeyManager |
Roles, tabindex, sync |
Step-by-step implementation
Permalink to "Step-by-step implementation"Step 1 — Native tables with row headers (SC 1.3.1)
Permalink to "Step 1 — Native tables with row headers (SC 1.3.1)"<table mat-table [dataSource]="ds" aria-labelledby="inv-h"> … <th mat-cell *matCellDef="let r" scope="row">{{ r.id }}</th>
See Angular Material table with MatSort accessibility.
Step 2 — One announcer (SC 4.1.3)
Permalink to "Step 2 — One announcer (SC 4.1.3)"this.dataAnnouncer.announce(`Sorted by ${label}`, 'sort');
See Angular CDK a11y: LiveAnnouncer and FocusMonitor.
Step 3 — Overlays and focus (SC 2.1.2, 2.4.3)
Permalink to "Step 3 — Overlays and focus (SC 2.1.2, 2.4.3)"this.dialog.open(FilterPanel).closed.subscribe(() => this.fm.focusVia(btn, origin));
Step 4 — Custom composites (SC 2.1.1)
Permalink to "Step 4 — Custom composites (SC 2.1.1)"new FocusKeyManager(items).withVerticalOrientation().withHomeAndEnd().withTypeAhead(400);
See Angular CDK ListKeyManager for custom grids.
Step 5 — PrimeNG configuration
Permalink to "Step 5 — PrimeNG configuration"See PrimeNG table accessibility.
Step 6 — Signals with gated announcements
Permalink to "Step 6 — Signals with gated announcements"effect(() => { const t = this.message(); if (this.userChanged) debounceAnnounce(t); });
See Angular signals and live announcements.
Keyboard interaction contract
Permalink to "Keyboard interaction contract"| Context | Key | Expected | Failure indicator |
|---|---|---|---|
| Sort header | Enter / Space | Sort; announcement | Silent |
| Paginator | Tab, Enter | Page change; announcement | Unlabelled icons |
| Custom list | Arrows, Home, End, typing | Roving focus | Every item a tab stop |
| Filter dialog | Tab | Contained; Escape closes | Focus escapes |
| After dialog | — | Focus back on trigger | Focus on body |
Screen reader compatibility matrix
Permalink to "Screen reader compatibility matrix"| Component / utility | NVDA + Chrome | JAWS + Chrome | VoiceOver + Safari | Notes |
|---|---|---|---|---|
| mat-table (native) | Good | Good | Good | Row headers recommended |
| MatSort + LiveAnnouncer | Sort state and message | Same | Same | Announcement is yours |
| p-table | Good | Good | Good | Translate aria strings |
| LiveAnnouncer | Reliable | Reliable | Reliable | Clear before repeats |
| ListKeyManager lists | Position and name | Same | Same | Set roles yourself |
Edge cases & failure modes
Permalink to "Edge cases & failure modes"1. Element-based mat-table
Permalink to "1. Element-based mat-table"Diagnosis: generic elements with roles; easy to break. Fix: native table markup.
2. Template aria-live regions
Permalink to "2. Template aria-live regions"Diagnosis: messages lost or duplicated. Fix: LiveAnnouncer through one service.
3. Ungated signal effects
Permalink to "3. Ungated signal effects"Diagnosis: announcements on load. Fix: intent gate and debounce.
4. No focus restore after overlays
Permalink to "4. No focus restore after overlays"Diagnosis: focus lands on body. Fix: Dialog restore or FocusMonitor focusVia.
5. Untranslated strings
Permalink to "5. Untranslated strings"Diagnosis: English paginator and selection labels. Fix: MatPaginatorIntl and PrimeNG translations.
Angular Material table with MatSort accessibility
Permalink to "Angular Material table with MatSort accessibility"Native table, row headers, sort descriptions, LiveAnnouncer messages, matNoDataRow and translated paginator. See Angular Material table with MatSort accessibility.
<th mat-header-cell *matHeaderCellDef mat-sort-header sortActionDescription="Sort by amount">Amount</th>
Angular CDK a11y — LiveAnnouncer and FocusMonitor
Permalink to "Angular CDK a11y — LiveAnnouncer and FocusMonitor"One region, a merging service, origin-aware focus and dialogs that restore focus. See Angular CDK a11y: LiveAnnouncer and FocusMonitor.
this.live.clear(); this.live.announce(text, 'polite');
Angular CDK ListKeyManager for custom grids
Permalink to "Angular CDK ListKeyManager for custom grids"FocusKeyManager or ActiveDescendantKeyManager for roving focus, configured per pattern, extended to two dimensions when needed. See Angular CDK ListKeyManager for custom grids.
this.km.change.subscribe(i => items.forEach((it, idx) => it.tabindex = idx === i ? 0 : -1));
PrimeNG table accessibility
Permalink to "PrimeNG table accessibility"Documented support; configure names, labels, translations, a small feature set and announcements. See PrimeNG table accessibility.
<p-tableCheckbox [value]="inv" [ariaLabel]="'Select invoice ' + inv.id" />
Angular signals and live announcements
Permalink to "Angular signals and live announcements"Computed messages, gated effects, debounced user changes and silence on load and hydration. See Angular signals and live announcements.
message = computed(() => `${this.visible().length} invoices`);
Cross-cutting concerns
Permalink to "Cross-cutting concerns"Services over components. Put announcement, focus restoration and table presets in injectable services and shared components; components then stay small and consistent.
SSR and hydration. Angular’s server rendering delivers table markup early; guard browser-only utilities and avoid announcing during hydration.
Change detection. Announce after the DOM reflects the change — after afterNextRender or in an effect — so messages match what users find when they navigate.
Theming. Material and PrimeNG themes control focus and selection visuals; verify contrast in light and dark themes with token tests.
Worked example: an Angular finance suite
Permalink to "Worked example: an Angular finance suite"A finance suite with 40 Angular screens had LiveAnnouncer installed but used in only three places; elsewhere, components rendered their own aria-live elements or nothing. Tables used the element-based mat-table without row headers. The team introduced a DataAnnouncer service, migrated tables to native markup with row headers and sort descriptions, added a translated paginator intl, standardised dialogs on CDK Dialog with focus restoration, and wrote a signals helper for gated announcements. A lint rule now flags aria-live in templates. Screen reader testing showed consistent announcements across all screens for the first time.
Who is affected, and how
Permalink to "Who is affected, and how"| User group | Typical failure | Angular fix |
|---|---|---|
| Screen reader users | Silent sorting and filtering | LiveAnnouncer service |
| Keyboard users | Focus lost after dialogs | CDK Dialog restore |
| Keyboard users | Many tab stops in lists | ListKeyManager |
| Low-vision users | Weak focus in themes | Keyboard focus styles |
| Non-English users | English library strings | Intl providers and translations |
Choosing between Angular Material and PrimeNG
Permalink to "Choosing between Angular Material and PrimeNG"Both libraries render semantic tables with accessible sort headers, and both need names, announcements and translations from you. Angular Material is lighter and composable: you add features (filtering, selection, expansion) yourself, which keeps tables simple and predictable. It builds directly on the CDK, so its accessibility utilities fit naturally. PrimeNG offers more out of the box — filter menus, frozen columns, column toggles, export — which saves time but increases the number of controls and configurations to verify. Teams with strict accessibility needs often prefer Material’s smaller surface; teams needing many features quickly often choose PrimeNG with a deliberately limited feature set.
Evaluating a component before adoption
Permalink to "Evaluating a component before adoption"For any Angular table or list component, check five things in an afternoon: native table markup or correct grid roles; keyboard sorting with aria-sort; labelled selection; meaningful empty and loading states; and hooks for announcements (events you can subscribe to). Then test with NVDA and VoiceOver for twenty minutes. If a component lacks events for sort and filter changes, wrapping it will be painful — prefer one that exposes them.
An audit script for Angular data screens
Permalink to "An audit script for Angular data screens"Search the codebase for aria-live in templates (each is a candidate for the announcer service), for <mat-table> element syntax (candidates for native markup), and for cdkTrapFocus on non-modal panels. Then, per screen, run the screen reader steps from the mat-table guide. The code search alone usually finds most systemic issues in an hour.
Design system integration
Permalink to "Design system integration"Ship an Angular accessibility package with the DataAnnouncer service, the signals helper, focus restoration helpers, ListKeyManager-based list and grid primitives, a translated MatPaginatorIntl, mat-table and p-table presets, and focus styles. Add lint rules against template live regions and element-based tables. Document which CDK utilities to use for which problem so teams stop rebuilding them.
Common mistakes in Angular data apps
Permalink to "Common mistakes in Angular data apps"- CDK installed, not used — hand-rolled live regions and traps alongside it.
- Element-based tables without role audits.
- Announcements in templates that re-render.
- Ungated effects after migrating to signals.
- Library strings untranslated in multilingual apps.
Measuring quality
Permalink to "Measuring quality"Track per screen: native table markup (yes/no), row headers (yes/no), sort and filter announcements (yes/no), focus restored after overlays (yes/no), and translated library strings (yes/no). Add a count of template aria-live occurrences across the codebase; driving it to zero is a good proxy for announcement consistency.
Migration path for existing apps
Permalink to "Migration path for existing apps"Most existing Angular apps can improve incrementally. First, add the announcer service and replace template live regions one screen at a time. Second, migrate element-based tables to native markup as screens are touched for other reasons. Third, standardise overlays on CDK Dialog. Fourth, introduce ListKeyManager-based primitives for custom lists. Each step is independent, delivers value on its own, and can be tracked as a small accessibility debt item rather than a large rewrite.
Where to start
Permalink to "Where to start"Teams new to Angular accessibility should begin with three changes that pay off across every screen: a shared announcer service built on LiveAnnouncer, sort headers that set aria-sort and confirm the change, and focus restoration after dialogs and deletions. Each takes a day or less and fixes the issues users report most often in data apps.
Testing checklist
Permalink to "Testing checklist"Automated
Permalink to "Automated"Keyboard
Permalink to "Keyboard"Screen reader
Permalink to "Screen reader"FAQ
Permalink to "FAQ"Is Angular Material's table accessible?
With native table markup, row headers, sort descriptions and announcements, yes — it is one of the strongest framework tables.
Should I use LiveAnnouncer or my own region?
LiveAnnouncer, through a small service that merges and gates messages.
When do I need ListKeyManager?
For any custom composite: record lists, toolbars, custom tabs or grids not provided by a library.
Do signals change accessibility patterns?
No, but they make it easy to derive messages; gate effects to avoid announcing on load.
Should I use PrimeNG or Material?
Choose by design language and features; both can be made accessible with the configuration described here.
How do I handle route changes?
Update titles and move focus to the main heading on navigation, using Router events; see the route change guide.
How do I test CDK utilities?
Use the CDK’s testing harnesses where available, spy on LiveAnnouncer, and assert focus in component tests.
What about Angular CDK virtual scroll?
It works with care for positions and focus; see the CDK virtual scroll guide.
Can Angular Material's table be virtualized?
Yes, with the CDK virtual scroll viewport and care for row positions and focus; for very large data, consider a grid library.
How should we name tables in Angular templates?
Use aria-labelledby pointing at the visible heading above the table; avoid aria-label strings that duplicate or drift from visible text.
Is there a lint rule for Angular accessibility?
angular-eslint includes template accessibility rules (for example, label association and valid ARIA). Enable them, and add custom rules such as flagging aria-live in templates.
What about Angular CDK testing harnesses?
Material component harnesses make it easier to drive tables, sorts and paginators in tests; combine them with LiveAnnouncer spies to test announcements.
Where should accessibility presets live in an Nx or monorepo setup?
In a shared library consumed by every app, versioned with the design system, so fixes reach all apps through one dependency update.
Related
Permalink to "Related"- React data grid libraries — React equivalents
- Vue & Nuxt data components — Vue equivalents
- Focus management in single-page apps — route and overlay focus
- Screen reader announcement strategies — message design
- Composite widget roles & states — patterns behind ListKeyManager