The Angular CDK A11y module provides comprehensive accessibility features including focus management, keyboard navigation, live announcers, and ARIA support for building accessible components.
Service for monitoring focus state and origin tracking to understand how elements gained focus (mouse, keyboard, touch, or programmatically).
/**
* Service for monitoring focus state and origin tracking
*/
class FocusMonitor {
/**
* Monitor focus changes on an element
* @param element - Element to monitor
* @param checkChildren - Whether to check child elements
* @returns Observable that emits focus origin changes
*/
monitor(element: HTMLElement | ElementRef, checkChildren?: boolean): Observable<FocusOrigin>;
/**
* Stop monitoring focus changes on an element
* @param element - Element to stop monitoring
*/
stopMonitoring(element: HTMLElement | ElementRef): void;
/**
* Focus an element with a specific origin
* @param element - Element to focus
* @param origin - Focus origin
* @param options - Focus options
*/
focusVia(element: HTMLElement | ElementRef, origin: FocusOrigin, options?: FocusOptions): void;
}
/**
* Focus origin indicates how an element gained focus
*/
type FocusOrigin = 'touch' | 'mouse' | 'keyboard' | 'program' | null;
/**
* Options for focus behavior
*/
interface FocusOptions {
preventScroll?: boolean;
}Usage Example:
import { FocusMonitor } from '@angular/cdk/a11y';
constructor(private focusMonitor: FocusMonitor) {}
ngAfterViewInit() {
// Monitor button focus
this.focusMonitor.monitor(this.buttonRef).subscribe(origin => {
if (origin === 'keyboard') {
console.log('Button focused via keyboard');
} else if (origin === 'mouse') {
console.log('Button focused via mouse');
}
});
}
// Focus with specific origin
focusButton() {
this.focusMonitor.focusVia(this.buttonRef, 'program');
}Service for managing ARIA describedby attributes and creating accessible descriptions.
/**
* Service for creating and managing ARIA describedby elements
*/
class AriaDescriber {
/**
* Add an ARIA description to an element
* @param hostElement - Element to describe
* @param message - Description message or element
* @param role - Optional ARIA role
*/
describe(hostElement: Element, message: string | HTMLElement, role?: string): void;
/**
* Remove an ARIA description from an element
* @param hostElement - Element to remove description from
* @param message - Description message or element to remove
* @param role - Optional ARIA role
*/
removeDescription(hostElement: Element, message: string | HTMLElement, role?: string): void;
/**
* Clean up service resources
*/
ngOnDestroy(): void;
}Key managers handle keyboard navigation for lists, menus, and other focusable collections.
/**
* Base key manager for keyboard navigation in lists
*/
abstract class ListKeyManager<T extends Focusable> {
/**
* Set the active item by index
* @param index - Index of item to activate
*/
setActiveItem(index: number): void;
/**
* Set the active item by reference
* @param item - Item to activate
*/
setActiveItem(item: T): void;
/**
* Handle keyboard events
* @param event - Keyboard event
*/
onKeydown(event: KeyboardEvent): void;
/**
* Set focus origin for focus management
* @param origin - Focus origin
*/
setFocusOrigin(origin: FocusOrigin): this;
}
/**
* Key manager that focuses items
*/
class FocusKeyManager<T extends Focusable> extends ListKeyManager<T> {
/**
* Set keyboard focus to active item
*/
setActiveItem(index: number): void;
setActiveItem(item: T): void;
}
/**
* Key manager using active descendant pattern for accessibility
*/
class ActiveDescendantKeyManager<T extends Highlightable> extends ListKeyManager<T> {
/**
* Set active descendant instead of moving focus
*/
setActiveItem(index: number): void;
setActiveItem(item: T): void;
}
/**
* Interface for focusable items
*/
interface Focusable {
focus(origin?: FocusOrigin): void;
}
/**
* Interface for highlightable items
*/
interface Highlightable {
setActiveStyles(): void;
setInactiveStyles(): void;
}Usage Example:
import { FocusKeyManager } from '@angular/cdk/a11y';
import { QueryList } from '@angular/core';
@Component({
selector: 'app-menu',
template: `
<div (keydown)="onKeydown($event)">
<app-menu-item #items *ngFor="let item of menuItems">
{{ item.label }}
</app-menu-item>
</div>
`
})
export class MenuComponent implements AfterViewInit {
@ViewChildren('items') menuItems!: QueryList<MenuItemComponent>;
private keyManager!: FocusKeyManager<MenuItemComponent>;
ngAfterViewInit() {
this.keyManager = new FocusKeyManager(this.menuItems)
.withWrap()
.withVerticalOrientation();
}
onKeydown(event: KeyboardEvent) {
this.keyManager.onKeydown(event);
}
}Directives for focus monitoring and management.
/**
* Directive for monitoring focus changes on elements
*/
@Directive({
selector: '[cdkMonitorElementFocus], [cdkMonitorSubtreeFocus]'
})
class CdkMonitorFocus {
/**
* Event emitted when focus origin changes
*/
@Output() cdkFocusChange: EventEmitter<FocusOrigin>;
}
/**
* Directive for trapping focus within an element
*/
@Directive({
selector: '[cdkTrapFocus]'
})
class CdkTrapFocus {
/**
* Whether focus trap is enabled
*/
@Input() cdkTrapFocus: boolean;
/**
* Whether to auto-capture focus when trap is enabled
*/
@Input() cdkTrapFocusAutoCapture: boolean;
}Service for making announcements to screen readers.
/**
* Service for making live announcements to screen readers
*/
class LiveAnnouncer {
/**
* Make an announcement to screen readers
* @param message - Message to announce
* @param politeness - ARIA live politeness setting
* @param duration - How long to keep the message
*/
announce(message: string, politeness?: AriaLivePoliteness, duration?: number): void;
/**
* Clear any existing announcements
*/
clear(): void;
/**
* Clean up service resources
*/
ngOnDestroy(): void;
}
/**
* ARIA live region politeness levels
*/
type AriaLivePoliteness = 'off' | 'polite' | 'assertive';Injection tokens for configuring accessibility features.
/**
* Configuration options for FocusMonitor
*/
interface FocusMonitorOptions {
detectionMode?: FocusMonitorDetectionMode;
}
/**
* Focus monitor detection modes
*/
const enum FocusMonitorDetectionMode {
IMMEDIATE,
EVENTUAL
}
/**
* Injection token for FocusMonitor default options
*/
const FOCUS_MONITOR_DEFAULT_OPTIONS: InjectionToken<FocusMonitorOptions>;
/**
* Injection token for input modality detector options
*/
const INPUT_MODALITY_DETECTOR_OPTIONS: InjectionToken<InputModalityDetectorOptions>;/**
* Angular module that includes all accessibility features
*/
@NgModule({
imports: [CommonModule, PlatformModule],
declarations: [
CdkMonitorFocus,
CdkTrapFocus,
CdkAriaLive
],
exports: [
CdkMonitorFocus,
CdkTrapFocus,
CdkAriaLive
]
})
class A11yModule {}import { FocusMonitor, FocusOrigin } from '@angular/cdk/a11y';
@Component({
selector: 'app-button',
template: '<button #button><ng-content></ng-content></button>'
})
export class ButtonComponent implements AfterViewInit, OnDestroy {
@ViewChild('button') buttonElement!: ElementRef<HTMLButtonElement>;
constructor(private focusMonitor: FocusMonitor) {}
ngAfterViewInit() {
this.focusMonitor.monitor(this.buttonElement).subscribe((origin: FocusOrigin) => {
// Add visual focus indicators based on origin
if (origin === 'keyboard') {
this.buttonElement.nativeElement.classList.add('keyboard-focused');
} else {
this.buttonElement.nativeElement.classList.remove('keyboard-focused');
}
});
}
ngOnDestroy() {
this.focusMonitor.stopMonitoring(this.buttonElement);
}
focus() {
this.focusMonitor.focusVia(this.buttonElement, 'program');
}
}import { FocusKeyManager } from '@angular/cdk/a11y';
import { UP_ARROW, DOWN_ARROW, ENTER } from '@angular/cdk/keycodes';
@Component({
selector: 'app-list',
template: `
<ul (keydown)="handleKeydown($event)" [attr.aria-activedescendant]="activeItem?.id">
<app-list-item
#items
*ngFor="let item of items"
[item]="item"
(click)="selectItem(item)">
</app-list-item>
</ul>
`
})
export class ListComponent implements AfterViewInit {
@ViewChildren('items') listItems!: QueryList<ListItemComponent>;
private keyManager!: FocusKeyManager<ListItemComponent>;
ngAfterViewInit() {
this.keyManager = new FocusKeyManager(this.listItems)
.withWrap()
.withVerticalOrientation()
.withHomeAndEnd();
}
handleKeydown(event: KeyboardEvent) {
switch (event.keyCode) {
case UP_ARROW:
case DOWN_ARROW:
this.keyManager.onKeydown(event);
break;
case ENTER:
this.selectItem(this.keyManager.activeItem?.item);
break;
}
}
get activeItem() {
return this.keyManager.activeItem;
}
}