Keyboard shortcuts
Register a global or page-scoped keyboard shortcut that works on every layout, with a self-documenting help dialog and no manual cleanup.
A keyboard shortcut usually means three things to get right at once: comparing the right kind of key across
keyboard layouts, not stealing a keystroke a text field or another component needs, and keeping a help list
that does not drift from what is actually registered. KeyboardShortcutsService is one keydown listener, one
registry, and a [keyShortcut] directive for the common case — built to replace an older engine that could
not be made correct on a non-US layout.
Concept — physical vs. character key
event.key is the character a key produces — layout-dependent, so "1" unshifted is & on an AZERTY
keyboard. event.code is the physical position — layout-independent. Digits are compared by event.code
here specifically because a written Alt+1 should reach the same physical key everywhere; every other key is
compared by event.key, since a printed letter is what a person actually reads off their keyboard.
Minimal example
import type { ApplicationConfig } from "@angular/core";
import { provideKeyboardShortcuts } from "@sinequa/atomic-angular";
export const appConfig: ApplicationConfig = {
providers: [provideKeyboardShortcuts()],
};import { Component } from "@angular/core";
import { ShortcutDirective } from "@sinequa/atomic-angular";
@Component({
selector: "sample-component",
imports: [ShortcutDirective],
template: `
<button keyShortcut="Alt+3" shortcutLabelKey="shortcuts.zones.filters" (click)="focusFilters()">
Go to the filters
</button>
`,
})
export class SampleComponent {
protected focusFilters() {
/* ... */
}
}[keyShortcut] registers the combination, writes aria-keyshortcuts on the host so assistive technology
announces it, and activates the host when the combination fires — the same (click) serves the mouse, the
Tab-and-Enter path, and the shortcut, in one code path. The registration disappears with the element, which is
what gives a page-scoped shortcut with nothing to clean up by hand.
How it works
Capture phase, deliberately: several components in this library stop propagation on keydown of their own
(ngCombobox on Enter and the arrows, a galactik ListItem on Delete, dialogs on Escape) — a bubble-phase
listener would be at their mercy. The service only ever consumes an event once a registration actually
matches, so it does not steal a key a component below it wants.
One stack per combination: the last registration wins, and the previous one comes back once it unregisters — modeling what a panel opening over a page should do to the shortcuts underneath it.
Recipes
Registering without an element
For a shortcut with no control behind it — a help dialog, a diagnostics panel.
import { Component, DestroyRef, inject } from "@angular/core";
import { KeyboardShortcutsDialog, KeyboardShortcutsService } from "@sinequa/atomic-angular";
@Component({
selector: "app-shell",
template: ``,
})
export class AppShellComponent {
private readonly keyboard = inject(KeyboardShortcutsService);
constructor() {
// destroyRef passed explicitly: a forgotten global shortcut is a mute bug,
// so the lifetime it is tied to is made visible at the call site.
this.keyboard.register(
{
combo: ["Alt+0", "Shift+?"],
labelKey: "shortcuts.help",
global: true,
run: () => void KeyboardShortcutsDialog.call(),
},
{ destroyRef: inject(DestroyRef) },
);
}
}KeyboardShortcutsDialog lists whatever is currently registered, grouped by categoryKey — it observes the
live registry rather than a hand-written list, which is what keeps the help dialog from drifting away from
what actually works. Opened from a page that registers nothing, it lists nothing, deliberately, rather than a
list half of which does not apply.
Options
Prop
Type
[keyShortcut] mirrors this as inputs: keyShortcut (the combo, required), shortcutLabelKey,
shortcutCategoryKey, shortcutDisabled (unregisters while true), shortcutHidden, shortcutGlobal.
Pitfalls
What's next
Feature flags
Every key of the app's general.features/feedback configuration, which ones actually gate behavior, and the live admin editor for testing them.
User settings
The signal store that persists a signed-in user's preferences — language, theme, bookmarks, recent and saved searches, baskets, alerts — to the backend.