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"
Angular accessibility toolkit for data UIs Layers: Angular Material and PrimeNG components at the top; CDK a11y utilities (LiveAnnouncer, FocusMonitor, FocusTrap, ListKeyManager) beneath; signals for state and messages; and your services that tie them together at the base. Angular accessibility toolkit for data UIsComponentsmat-table, MatSort, MatPaginator, p-tableCDK a11yLiveAnnouncer, FocusMonitor, FocusTrap, ListKeyManagerSignalsstate and computed messagesYour servicesDataAnnouncer, focus restore, presets
Most behaviour already exists; consistency comes from your services.

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.

Angular table options compared Matrix of options — mat-table, p-table, CDK table (unstyled), a grid library such as AG Grid — against semantics, sorting accessibility, features, styling control and announcements. Angular table options comparedSemanticsSorting a11yFeaturesStylingAnnouncementsmat-tableNative tableMatSortCompose yourselfMaterialAddp-tableNative tablepSortableColumnMany built inThemesAddCDK tableNative tableYour codeMinimalFull controlAddAG Grid (Angular)GridBuilt inEnterpriseThemesAdd
mat-table and p-table cover most needs; grid libraries for cell-level interaction.

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.

A filter change in an Angular data page Sequence among filter input, signals, table, DataAnnouncer and LiveAnnouncer: typing updates the filter signal; the table re-renders; the computed message changes; the gated effect debounces and calls the service, which clears and announces. A filter change in an Angular data pageFilter inputSignalsTableAnnouncerfilter.set("overdue")visible rows recomputedmessage changed; gate opendebounce 400 ms;clear"42 invoices"
Each piece has one job; together they produce one clear message.

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.

Permalink to "Related"

← Back to Accessible Data Component Libraries