import { Component, signal } from '@angular/core'; @Component({ selector: 'app-keyboard-nav', standalone: true, template: ` <div role="list" tabindex="0" (keydown)="onKeydown($event)"> <button *ngFor="let item of items; let i = index" [attr.aria-selected]="i === focusedIndex()" [class.focused]="i === focusedIndex()" (click)="select(i)" tabindex="-1"> {{item}} </button> </div> `, styles: [`.focused { outline: 2px solid blue; }`] }) export class KeyboardNavComponent { items = ['One', 'Two', 'Three']; focusedIndex = signal(0); onKeydown(event: KeyboardEvent) { const maxIndex = this.items.length - 1; switch(event.key) { case 'ArrowDown': this.focusedIndex.set((this.focusedIndex() < maxIndex) ? this.focusedIndex() + 1 : 0); event.preventDefault(); break; case 'ArrowUp': this.focusedIndex.set((this.focusedIndex() > 0) ? this.focusedIndex() - 1 : maxIndex); event.preventDefault(); break; } } select(index: number) { this.focusedIndex.set(index); } }
Pressing 'ArrowDown' increases the focusedIndex by 1 unless it is at the last item, then it cycles back to 0. This moves the focus visually to the next button.
onKeydown(event: KeyboardEvent) {
switch(event.key) {
case 'ArrowLeft':
this.moveFocusLeft();
break;
case 'ArrowRight':
this.moveFocusRight();
break;
default:
break;
}
}In TypeScript and JavaScript, each statement should end with a semicolon. The missing semicolon after 'break' in the 'ArrowLeft' case is a syntax error.
items = ['A', 'B', 'C']; focusedIndex = signal(0); onKeydown(event: KeyboardEvent) { const maxIndex = this.items.length - 1; switch(event.key) { case 'ArrowUp': this.focusedIndex.set((this.focusedIndex() > 0) ? this.focusedIndex() - 1 : maxIndex); break; } }
Starting at 0, pressing ArrowUp sets focusedIndex to maxIndex (2). Pressing ArrowUp again decreases it to 1.
template: <div role="list" tabindex="0" (keydown)="onKeydown($event)"> <button *ngFor="let item of items; let i = index" [attr.aria-selected]="i === focusedIndex()" [class.focused]="i === focusedIndex" tabindex="-1"> {{item}} </button> </div> component: focusedIndex = signal(0); onKeydown(event: KeyboardEvent) { if(event.key === 'ArrowDown') { this.focusedIndex.set((this.focusedIndex() + 1) % this.items.length); event.preventDefault(); } }
In Angular signals, you must call the signal as a function (focusedIndex()) to get its current value. Using 'focusedIndex' without parentheses does not update the class binding.
Using role="list" and role="listitem" correctly describes the structure. tabindex="-1" on items allows programmatic focus management without tab stops on all items, improving keyboard navigation.