RatedWithAI

RatedWithAI

Accessibility scanner

Angular Accessibility Guide 2026: WCAG Compliance for Angular Apps

Updated June 2026·13 min read·Developer Guide

Angular has the most built-in accessibility tooling of any major frontend framework — the Angular CDK's a11y module includes focus management utilities, live announcer service, and keyboard event handling. But CDK availability doesn't mean Angular apps are accessible by default. This guide covers where Angular apps fail WCAG 2.1 AA and the specific Angular patterns that fix them.

Quick Summary

  • Angular CDK's a11y module handles focus trapping, live announcements, and keyboard navigation
  • Angular Router doesn't announce navigation to screen readers by default — fix with LiveAnnouncer
  • Angular Material components are ARIA-compliant out of the box; custom components often aren't
  • axe-core integrates with Karma and Jest for automated accessibility testing in CI
  • Angular's strict template checking catches some ARIA errors at compile time

The Most Common Angular Accessibility Mistakes

CriticalClick events on non-interactive elements

(click) on a <div> or <span> excludes keyboard and screen reader users. Use <button> for actions and <a routerLink> for navigation. If you must use a custom element, apply role="button", tabindex="0", and (keydown.enter)/(keydown.space) event bindings — but a semantic <button> is always preferable and requires no extra work.

CriticalAngular Router navigation without announcements

Angular Router's NavigationEnd fires when routing completes, but it doesn't announce the change to screen readers. Inject LiveAnnouncer from @angular/cdk/a11y and call liveAnnouncer.announce() in a NavigationEnd subscription to notify screen reader users that the page changed.

HighMissing ARIA on reactive template updates

Angular's change detection updates the DOM silently. Search results, form errors, and notification banners that appear reactively won't be announced to screen readers unless wrapped in an aria-live region or announced via Angular CDK's LiveAnnouncer service.

HighForm controls without accessible labels

Angular Reactive Forms and Template-Driven Forms don't automatically associate labels with inputs. Use <label [for]="controlId"> with a matching [id] on the input, or [attr.aria-label] for inputs where a visible label isn't possible. placeholder is not an accessible label.

HighCustom overlay/modal components

Angular's *ngIf-based modal patterns don't trap focus by default. Use Angular CDK's FocusTrap directive (cdkTrapFocus) on your modal container, and cdkTrapFocusAutoCapture to move focus inside when the modal opens. Return focus to the trigger element on close.

MediumIncorrect use of routerLinkActive

routerLinkActive adds a CSS class but doesn't set aria-current="page" on the active navigation link. Add [attr.aria-current]="isActive ? 'page' : null" to active navigation links so screen reader users know which page they're on.

Angular CDK a11y Module

The Angular CDK's accessibility module is one of Angular's biggest advantages for building accessible UIs. Install it once and use across your app:

LiveAnnouncer — Announce Route Changes

import { LiveAnnouncer } from '@angular/cdk/a11y'
import { Router, NavigationEnd } from '@angular/router'

@Component({ ... })
export class AppComponent implements OnInit {
  constructor(
    private router: Router,
    private liveAnnouncer: LiveAnnouncer,
    private titleService: Title
  ) {}

  ngOnInit() {
    this.router.events.pipe(
      filter(e => e instanceof NavigationEnd)
    ).subscribe(() => {
      const title = this.titleService.getTitle()
      this.liveAnnouncer.announce(`Navigated to ${title}`, 'polite')
    })
  }
}

FocusTrap — Modal Dialogs

<!-- dialog.component.html -->
<div
  role="dialog"
  aria-modal="true"
  [attr.aria-labelledby]="titleId"
  cdkTrapFocus
  cdkTrapFocusAutoCapture
>
  <h2 [id]="titleId">{{ title }}</h2>
  <div class="dialog-body">
    <ng-content></ng-content>
  </div>
  <button (click)="close()">Close</button>
</div>

FocusMonitor — Track Focus State

import { FocusMonitor } from '@angular/cdk/a11y'

@Component({ ... })
export class ButtonComponent implements OnInit, OnDestroy {
  constructor(
    private focusMonitor: FocusMonitor,
    private el: ElementRef
  ) {}

  ngOnInit() {
    // Detects keyboard vs mouse focus — show focus ring only for keyboard
    this.focusMonitor.monitor(this.el, true).subscribe(origin => {
      this.showFocusRing = origin === 'keyboard'
    })
  }

  ngOnDestroy() {
    this.focusMonitor.stopMonitoring(this.el)
  }
}

Automated Accessibility Testing in Angular

Use axe-core with your existing test setup to catch WCAG violations automatically in CI:

Jest + axe-core

import { ComponentFixture, TestBed } from '@angular/core/testing'
import { axe } from 'jest-axe'
import { MyFormComponent } from './my-form.component'

describe('MyFormComponent accessibility', () => {
  let fixture: ComponentFixture<MyFormComponent>

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [MyFormComponent]
    }).compileComponents()
    fixture = TestBed.createComponent(MyFormComponent)
    fixture.detectChanges()
  })

  it('should have no accessibility violations', async () => {
    const results = await axe(fixture.nativeElement)
    expect(results).toHaveNoViolations()
  })
})

For Playwright e2e tests, use @axe-core/playwright to run accessibility checks on full rendered pages with real CSS applied — catching contrast violations that unit tests miss.

Angular Material: What's Accessible, What's Not

Angular Material components are generally WCAG 2.1 AA compliant out of the box. But there are important caveats:

Good
MatDialog: Focus trap and aria-modal built in. Ensure aria-labelledby points to dialog title.
Good
MatSelect: ARIA combobox pattern implemented. Screen reader compatible.
Caution
MatTable: Add caption and scope to <th> elements manually. Virtual scroll tables need additional ARIA.
Caution
MatDatepicker: Keyboard navigation works, but date format announcements vary by screen reader. Test with NVDA.
Caution
MatTooltip: Tooltips don't always announce on focus — use aria-describedby on the trigger element explicitly.
Good
MatStepper: aria-current, aria-label, and step roles implemented. Verify in NVDA.
Manual
Custom overlays via Overlay CDK: No built-in accessibility. Add role, aria-modal, focus trap, and escape handling yourself.

Angular Accessibility Checklist

Install @angular/cdk and import A11yModule in your shared module
Add LiveAnnouncer to AppComponent to announce route changes
Update document title on every NavigationEnd (TitleService)
Use cdkTrapFocus on all modal/dialog overlays
Apply [attr.aria-current]="'page'" to active nav links
Wrap reactive content (search results, errors, toasts) in aria-live regions
Associate all form controls with visible <label> elements or aria-label
Run axe-core tests in Jest or Karma against all components
Add Playwright axe-core scan to e2e suite for full-page contrast checking
Test keyboard navigation across all views (Tab, Shift+Tab, arrow keys, Enter, Escape)
Test with NVDA + Firefox (Windows) or VoiceOver + Safari (macOS/iOS)
Verify Angular Material component ARIA against screen reader — don't assume compliance

Scan Your Angular App for WCAG Issues

Run an automated WCAG 2.1 scan on your Angular production URL. Catches contrast violations, missing landmarks, and ARIA errors that don't appear in unit tests.

Sponsored

Find Angular accessibility issues in your browser

Deque Axe DevTools Pro integrates with Chrome and Firefox DevTools — run WCAG checks on rendered Angular components as you develop, before CI.

Try Free →

Related Guides