Lumeo

Keyboard Shortcut Service

Register global keyboard shortcuts from C# code with automatic cleanup.

Quick Start

The KeyboardShortcutService lets you register keyboard shortcuts that work globally across the page. Inject it, register shortcuts in OnAfterRenderAsync, and dispose them when the component unmounts.

@implements IAsyncDisposable @inject KeyboardShortcutService Shortcuts @code { private IAsyncDisposable? _shortcut; protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { _shortcut = await Shortcuts.RegisterAsync("ctrl+k", async () => { // Open search, show command palette, etc. await InvokeAsync(StateHasChanged); }); } } public async ValueTask DisposeAsync() { if (_shortcut is not null) await _shortcut.DisposeAsync(); } }

Live Demo

A keyboard shortcut is registered on this page right now. Try pressing the key combination below to see it in action.

Press

Ctrl

+

M

to trigger the shortcut

Hit count

0

Last triggered

Never

The code behind this demo registers ctrl+m in OnAfterRenderAsync and disposes it when you navigate away.

@implements IAsyncDisposable @inject KeyboardShortcutService Shortcuts <Lumeo.Text>Hit count: @_hitCount</Lumeo.Text> <Lumeo.Text>Last triggered: @_lastTriggered</Lumeo.Text> @code { private IAsyncDisposable? _shortcut; private int _hitCount; private DateTime? _lastTriggered; protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { _shortcut = await Shortcuts.RegisterAsync("ctrl+m", async () => { _hitCount++; _lastTriggered = DateTime.Now; await InvokeAsync(StateHasChanged); }); } } public async ValueTask DisposeAsync() { if (_shortcut is not null) await _shortcut.DisposeAsync(); } }

Key Combo Format

Key combos use modifier+key format, separated by +. Key names use KeyboardEvent.key values.

Combo Keys Description
ctrl+k
CtrlK
Common for search / command palette
ctrl+shift+p
CtrlShiftP
Multi-modifier combo
alt+n
AltN
Alt modifier
meta+enter
CmdEnter
Cmd (Mac) or Win (Windows)
escape Esc No modifier needed
f1 F1 Function keys

Available modifiers: ctrl, shift, alt, meta. Order does not matter -- shift+ctrl+k and ctrl+shift+k are identical. The service normalizes the combo internally.

Examples

Async Handler

Use an async lambda when your handler needs to await operations like API calls or navigation.

_shortcut = await Shortcuts.RegisterAsync("ctrl+s", async () => { await SaveAsync(); await InvokeAsync(StateHasChanged); });

Sync Handler

Use a synchronous Action for simple state changes.

_shortcut = await Shortcuts.RegisterAsync("escape", () => { _isOpen = false; StateHasChanged(); });

Disable preventDefault

By default, preventDefault is true, which stops the browser's native behavior for that key combo. Pass false to let the browser action proceed alongside your handler.

// Browser's ctrl+p (print dialog) still opens, but your handler also fires _shortcut = await Shortcuts.RegisterAsync("ctrl+p", HandlePrint, preventDefault: false);

Multiple Shortcuts

Register several shortcuts and dispose them all at once using a list.

private List<IAsyncDisposable> _shortcuts = new(); protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { _shortcuts.Add(await Shortcuts.RegisterAsync("ctrl+k", OpenSearch)); _shortcuts.Add(await Shortcuts.RegisterAsync("ctrl+shift+p", OpenCommandPalette)); _shortcuts.Add(await Shortcuts.RegisterAsync("escape", CloseOverlays)); } } public async ValueTask DisposeAsync() { foreach (var s in _shortcuts) await s.DisposeAsync(); }

Cleanup Pattern

Shortcut registrations are cleaned up by disposing the IAsyncDisposable handle returned from RegisterAsync. This is the recommended pattern. Alternatively, you can call UnregisterAsync(id) manually.

Single shortcut

@implements IAsyncDisposable @code { private IAsyncDisposable? _shortcut; protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) _shortcut = await Shortcuts.RegisterAsync("ctrl+k", HandleSearch); } public async ValueTask DisposeAsync() { if (_shortcut is not null) await _shortcut.DisposeAsync(); } }

Multiple shortcuts with List

@implements IAsyncDisposable @code { private readonly List<IAsyncDisposable> _shortcuts = new(); protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { _shortcuts.Add(await Shortcuts.RegisterAsync("ctrl+k", OpenSearch)); _shortcuts.Add(await Shortcuts.RegisterAsync("ctrl+n", CreateNew)); _shortcuts.Add(await Shortcuts.RegisterAsync("escape", CloseAll)); } } public async ValueTask DisposeAsync() { foreach (var s in _shortcuts) await s.DisposeAsync(); } }

API Reference

Methods

Method Returns Description
RegisterAsync(keyCombo, Func<Task>, preventDefault?) ValueTask<IAsyncDisposable> Registers a shortcut with an async handler. Dispose the returned handle to unregister.
RegisterAsync(keyCombo, Action, preventDefault?) ValueTask<IAsyncDisposable> Registers a shortcut with a synchronous handler. Wraps the Action internally.
UnregisterAsync(id) ValueTask Manually unregister by ID. Prefer disposing the handle instead.

Parameters

Parameter Type Default Description
keyCombo string -- Key combination string, e.g. "ctrl+k", "escape", "ctrl+shift+p". Modifier order does not matter.
handler Func<Task> | Action -- Callback invoked when the shortcut is triggered. Call InvokeAsync(StateHasChanged) inside async handlers to update the UI.
preventDefault bool true Whether to call e.preventDefault() on the browser keyboard event. Set to false to allow the native browser action.