Enhancing A11y with Angular CDK

  1. Web Accessibility (A11y) in Angular – Introduction
  2. Accessibility Testing Tools for Angular
  3. Accessible Angular Routes
  4. ARIA roles and attributes in Angular
  5. Building Accessible Forms with Angular
  6. Enhancing A11y with Angular CDK

The @angular/cdk/a11y package is part of the Angular Component Dev Kit (CDK) and provides a collection of services, directives, SASS mixins, and other utilities to improve the Accessibility (A11y) of your Angular apps.

These tools are invaluable when building reusable, presentation-focused components with screen-reader announcements, robust keyboard and focus support, high-contrast theming, and more. They’re especially useful if you’re creating an Angular Component Library or a Design System.

Below are some of my favorites, along with brief code examples.

LiveAnnouncer for Screen Readers

The first service we want to highlight is the LiveAnnouncer. It allows you to announce messages to screen readers, which is particularly useful for providing feedback after user interactions. It's so simple: I often replace my console.log calls with announcements.

Here's an example of that:

private readonly liveAnnouncer = inject(LiveAnnouncer);
if (this.flights().length > 0) {
  // console.log('Found ' + this.flights().length + ' flights');
  this.liveAnnouncer.announce('Found ' + this.flights().length + ' flights');
} else {
  // console.log('No flights found');
  this.liveAnnouncer.announce('No flights found');
}

Make sure to test your announcements with the use of a screen reader.

Focus Tools

The Angular CDK also includes several tools supporting your focus management.

Focus Trap

The FocusTrap Directive allows you to trap focus within a specific element, ensuring that keyboard navigation remains within that element until the user explicitly exits it.

This is particularly useful for modal dialogs or pop-ups:

<dialog class="awesome-dialog" cdkTrapFocus>
  <!–- Yay, focus won't leave this element! -->
</dialog>

If you want focus to jump in automatically, enable the cdkTrapFocusAutoCapture flag:

<dialog class="awesome-dialog" cdkTrapFocus [cdkTrapFocusAutoCapture]="true">
  <!–- Yay, focus won't leave this element! -->
</dialog>

With this flag set to true, the focus will automatically be captured when the dialog opens. However, it may not always be the best choice to have an element automatically capture focus, so we can avoid this with a trick:

<dialog class="awesome-dialog" cdkTrapFocus [cdkTrapFocusAutoCapture]="true">
  <h3 class="awesome-dialog__title" tabindex="-1" cdkFocusInitial>...</h3>
  <!–- Yay, focus won't leave this element! -->
</dialog>

By adding cdkFocusInitial the title will be secretly focused initially. Since it has tabindex -1, it won't be focusable by keyboard navigation afterward.

Focus Regions

The Regions are a powerful tool that allows you to define a specific area of your application where focus should be managed. This is particularly useful for complex components like dropdowns or menus, where you want to ensure that focus remains within the component while it is open.

  • cdkFocusRegionStart
  • cdkFocusRegionEnd and optionally
  • cdkFocusInitial

Here's an example of how to use the Regions:

<nav>
  <a routerLink routerLinkActive="awesome" ariaCurrentWhenActive="page" cdkFocusRegionStart>Focus region start</a></li>
  <a routerLink routerLinkActive="awesome" ariaCurrentWhenActive="page">Another focusable link</a></li>
  <a routerLink routerLinkActive="awesome" ariaCurrentWhenActive="page" cdkFocusInitial>Initially focused</a></li>
  <a routerLink routerLinkActive="awesome" ariaCurrentWhenActive="page" cdkFocusRegionEnd>Focus region end</a></li>
</nav>

Note: To learn about the ariaCurrentWhenActive please read my post on Accessible Angular Routes

Focus Monitor

The FocusMonitor is a service that allows you to monitor focus changes within your application. It can be used to track when an element gains or loses focus, which is particularly useful for debugging focus-related issues.

This service can be injected into your components or services, and you can subscribe to focus changes:

import { Component, DestroyRef, effect, ElementRef, inject, viewChild } from '@angular/core';
import { FocusMonitor, FocusOrigin } from '@angular/cdk/a11y';

@Component({
  selector: 'app-navbar',
  template: `<nav #observed class="awesome-nav-cnt"><!-- children --></nav>`,
})
export class AwesomeFocusMonitorComponent {
  private readonly destroyRef = inject(DestroyRef);
  private readonly focusMonitor = inject(FocusMonitor);
  private readonly observedElementRef = viewChild.required<ElementRef<HTMLElement>>('observed');

  constructor() {
    effect(() => {
      const observedElementRef = this.observedElementRef(); // effect will run when the view is initialized
      this.focusMonitor.monitor(observedElementRef, true).subscribe((origin: FocusOrigin) => console.log(origin));
      this.destroyRef.onDestroy(() => this.focusMonitor.stopMonitoring(observedElementRef));
    });
  }
}

This is particularly useful for debugging focus-related issues, as it allows you to see when an element gains or loses focus.

FocusOrigin can be one of the following:

  • 'mouse' the element was focused with the mouse
  • 'keyboard' it was focused with the keyboard
  • 'touch' it was focused by touching on a touchscreen
  • 'program' it was focused programmatically, whereas
  • null indicates the element was blurred

So, this is also useful to check if a touch device is being used 😏

Styling utilities

The Angular A11y package also includes two useful Sass mixins.

Hidden Elements

Screen readers and other assistive technology skip elements that have display: none, visibility: hidden, opacity: 0, height: 0, or width: 0. In some cases, you may need to visually hide an element while keeping it available to assistive technology (e.g. Screen Readers).

@use '@angular/cdk';

@include cdk.a11y-visually-hidden();
<div class="awesome-toggle">
  <input type="checkbox" class="cdk-visually-hidden" />
</div>

High Contrast Mode

Some operating systems and/or devices include a High Contrast Mode. The Angular A11y package provides a Sass mixin that lets you define styles that only apply in high contrast mode. To create a high contrast style, just wrap it into the high-contrast mixin.

@use '@angular/cdk';

button {
  @include cdk.high-contrast {
    outline: 3px solid gold;
  }
}

The mixin works by targeting the forced-colors media query.

Accessibility Workshop

For those looking to deepen their Angular expertise, we offer a range of workshops – both in English and German:

Conclusion

With these tools, you can significantly enhance the accessibility of your Angular Apps. They help ensure that your components are not only functional but also user-friendly for everyone, including those with disabilities.

One last thing: In this Code lab by Google, you can find some exercises on the most important Angular CDK A11y tools https://codelabs.developers.google.com/angular-a11y#8

That's all folks!

This is the last edition of our A11y series. I hope you found it helpful and learned something new about making your Angular Apps more accessible and user-friendly for all of us, and that you are ready for the European Accessibility Act (EAA). If uncertain, please go back to the start of the A11y blog series.

This blog post was written by Alexander Thalhammer. Follow me on Linkedin, X or giThub.

References