Screen Reader Announcements with LiveAnnouncer
Among the utilities in the @angular/cdk/a11y package, the LiveAnnouncer service stands out. It lets you push messages to screen readers, which comes in handy whenever you need to give feedback after a user action. The beauty is in its simplicity — in many cases, I find myself swapping out console.log calls for announcements.
Here's what that looks like:
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');
}
Don't forget to verify your announcements with an actual screen reader.
Managing Focus
The CDK ships with a handful of utilities designed to help you keep keyboard focus exactly where it should be.
Focus Trap
With the FocusTrap Directive, you can confine keyboard navigation to a single element until the user chooses to leave it. It's an essential tool for modal dialogs or pop-ups where focus shouldn't wander:
<dialog class="awesome-dialog" cdkTrapFocus>
<!–- Yay, focus won't leave this element! -->
</dialog>
For automatic focus entry, there's the cdkTrapFocusAutoCapture flag:
<dialog class="awesome-dialog" cdkTrapFocus [cdkTrapFocusAutoCapture]="true">
<!–- Yay, focus won't leave this element! -->
</dialog>
With this flag on, focus is taken automatically as soon as the dialog appears. But an automatic focus grab isn't always the right UX, and there's a clever way around it:
<dialog class="awesome-dialog" cdkTrapFocus [cdkTrapFocusAutoCapture]="true">
<h3 class="awesome-dialog__title" tabindex="-1" cdkFocusInitial>...</h3>
<!–- Yay, focus won't leave this element! -->
</dialog>
Placing cdkFocusInitial on the title focuses it quietly at first. Since the title carries a tabindex of -1, it never shows up in normal keyboard navigation afterwards.
Focus Regions
With Regions, you can declare a section of the page where all focus activity should be contained. For intricate components like dropdowns or menus, this is the right approach when you want to keep focus within the component while it is open.
cdkFocusRegionStartcdkFocusRegionEndplus, optionally,cdkFocusInitial
An example of Regions in action:
<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: You'll find my explanation of ariaCurrentWhenActive in the post about Accessible Angular Routes.
Focus Monitor
The FocusMonitor service hooks you into focus-related events anywhere in your app. It reports when an element becomes focused or loses it — which makes it a fine debugging instrument for focus problems.
Inject the service into your components (or other services) and subscribe to focus changes like this:
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));
});
}
}
Because it reveals exactly when an element takes or drops focus, this service is a valuable ally when tracking down focus-related bugs.
FocusOrigin takes one of these values:
'mouse'when the pointer caused the focus'keyboard'when the keyboard did'touch'when a touchscreen did'program'for programmatic focusnullwhen the element has been blurred
So it can double as a way to detect a touch-enabled device 😏
Styling Helpers
Two Sass mixins complete the accessibility toolbox in the Angular A11y package.
Hidden Elements
Assistive technology — screen readers above all — will skip anything set to display: none, visibility: hidden, opacity: 0, height: 0, or width: 0. There are times, though, when an element should disappear from the screen yet stay readable for assistive technology (like 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
Many operating systems offer a High Contrast Mode. A Sass mixin from the Angular A11y package lets you add styles that are only applied when this mode is active. Wrap those styles in the high-contrast mixin:
@use '@angular/cdk';
button {
@include cdk.high-contrast {
outline: 3px solid gold;
}
}
The mixin achieves this by aiming at the forced-colors media query.
Take a Workshop
If you'd like to go deeper into Angular, we have workshops in both English and German waiting for you:
- ♿ Accessibility Workshop
- 📈 Best Practices Workshop (accessibility topics included)
- 🚀 Performance Workshop
Summing Up
These tools go a long way toward making your Angular Apps more inclusive. They keep your components functional, but also welcoming to every user, regardless of ability.
A final link: Google maintains an Angular CDK A11y codelab with hands-on exercises — https://codelabs.developers.google.com/angular-a11y#8
That's all folks!
That wraps up this A11y series. I hope you've picked up something useful about making your Angular Apps friendly for everyone, and that you're now prepared for the European Accessibility Act (EAA). If you need a refresher, just go back to the beginning of the A11y blog series.
This post was written by Alexander Thalhammer. You can follow him on Linkedin, X or github.
Further Reading
- Angular CDK Accessibility – official docs
- Google codelab on A11y by Emma Twersky
