# Keyboard Shortcuts

Source: https://lumeo.nativ.sh/docs/services/keyboard-shortcuts

# 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(); } }

Always register shortcuts inside `OnAfterRenderAsync(firstRender)` because JS interop is not available during server-side prerendering. The returned `IAsyncDisposable` handle automatically unregisters the shortcut when disposed.

## 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

Reset counter

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);

Be careful when overriding common browser shortcuts like Ctrl+C, Ctrl+V, or Ctrl+T. Always consider whether blocking the native behavior is appropriate for your users.

### 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(); } }

The `IAsyncDisposable` handle calls `UnregisterAsync` internally and handles `JSDisconnectedException` gracefully, so it is safe to dispose even when the circuit has disconnected.

## 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.
