# How to Implement Keyboard Shortcuts and Chrome Shortcuts in the Native SDK

> Learn to implement keyboard and Chrome shortcuts in the Native SDK. Discover how on_command and on_key work for efficient app control.

- Repository: [Vercel Labs/native](https://github.com/vercel-labs/native)
- Tags: how-to-guide
- Published: 2026-07-18

---

**The Native SDK exposes two distinct shortcut mechanisms—standard keyboard shortcuts routed through `on_command` and chrome shortcuts handled via `on_key`—both declared in `app.zon` and registered with the host OS through `platform.services.configureShortcuts`.**

The vercel-labs/native repository provides a declarative way to implement keyboard shortcuts and chrome shortcuts in Native SDK applications through manifest-driven configuration and runtime callbacks. By defining `Shortcut` entries in your `app.zon` and wiring them to `UiApp.Options`, you can handle global commands and window-level chrome events without blocking text input widgets.

## Declaring Keyboard and Chrome Shortcuts in the App Manifest

All shortcuts begin as declarations in the `app.zon` manifest file. The `src/tooling/manifest.zig` parser validates these entries via `validateShortcut` and stores them in `AppInfo.shortcuts` before the platform registers them with the operating system【/cache/repos/github.com/vercel-labs/native/main/src/tooling/manifest.zig#L49-L57】【/cache/repos/github.com/vercel-labs/native/main/src/platform/types.zig#L22-L27】.

### Understanding the Shortcut Struct

In `src/platform/types.zig`, the `native_sdk.Shortcut` struct defines the required fields for every shortcut entry:

```zig
pub const Shortcut = struct {
    id: []const u8,               // command identifier (must match a command in the manifest)
    key: []const u8,              // printable key name or a special name like "escape"
    modifiers: ShortcutModifiers = .{}, // at least one modifier is required for character keys
};

```

*Source:* `src/platform/types.zig` lines 82–99【/cache/repos/github.com/vercel-labs/native/main/src/platform/types.zig#L82-L99】.

A companion definition in `src/primitives/app_manifest/types.zig` mirrors the public struct for manifest parsing and enforces the `max_shortcuts` limit.

### Adding Shortcuts to app.zon

Define your shortcuts under the `shortcuts` array inside `app.zon`:

```zon
shortcuts: [
  { id = "command.palette", key = "k", modifiers = [ "command" ] },
  { id = "command.clear",   key = "escape" },
]

```

Each entry maps a key combination to a command `id` that the runtime will later forward to your application logic.

## Wiring Keyboard and Chrome Shortcuts to Application Logic

The runtime delivers shortcut events through two separate callbacks inside `UiApp.Options`. Choosing the correct callback depends on whether the shortcut is a standard command or a chrome shortcut.

### Using on_command for Standard Keyboard Shortcuts

**Keyboard shortcuts** are global shortcuts that fire when the user presses a registered key combination, such as `cmd+K`. The runtime translates the platform shortcut event into a `native_sdk.Shortcut` struct and forwards it to the app via the `on_command` callback.

In `src/runtime/ui_app.zig`, the `UiApp.Options` struct stores the `on_command` field around lines 78–81【/cache/repos/github.com/vercel-labs/native/main/src/runtime/ui_app.zig#L78-L81】:

```zig
const options = UiApp.Options{
    .name = "MyApp",
    .scene = ...,
    .canvas_label = "main",
    .on_command = onCommand,
};

fn onCommand(name: []const u8) ?Msg {
    return switch (name) {
        "command.palette" => Msg{ .ShowPalette = {} },
        "command.clear"   => Msg{ .Clear = {} },
        else => null,
    };
}

```

The callback receives the **command id** string defined in the manifest `id` field.

### Using on_key for Chrome Shortcuts

**Chrome shortcuts** are platform-specific window-level controls, such as play/pause or back gestures. According to the precedence comment in `src/runtime/ui_app.zig` lines 31–38, these events are **not** routed through the widget hierarchy. Instead, they are exposed through the `on_key` fallback only after the widget tree has declined the key【/cache/repos/github.com/vercel-labs/native/main/src/runtime/ui_app.zig#L31-L38】.

This design guarantees that chrome shortcuts never steal typed text from input fields.

```zig
const options = UiApp.Options{
    // ...
    .on_key = onKey,
};

fn onKey(event: canvas.WidgetKeyboardEvent) ?Msg {
    // `event.key` will be "space" etc.; modifiers are already forced to true.
    return switch (event.key) {
        "space" => Msg{ .TogglePlay = {} },
        else    => null,
    };
}

```

Because modifiers are already forced to true for these events, you only need to match against `event.key`.

## Platform Registration and Runtime Delivery

Shortcuts do not become active until the platform layer registers them with the host OS. This happens automatically when the app starts.

### Configuring Shortcuts per Platform

During initialization, the runtime calls `platform.services.configureShortcuts`. Each platform implementation receives the slice of `platform.Shortcut` structs and registers them with native OS APIs.

For example, `src/platform/macos/root.zig` contains the macOS registration routine:

```zig
fn configureShortcuts(context: ?*anyopaque, shortcuts: []const platform_mod.Shortcut) anyerror!void {
    // Platform-specific registration, e.g. NSMenu on macOS.
}

```

*Source:* `src/platform/macos/root.zig` lines 1766–1767【/cache/repos/github.com/vercel-labs/native/main/src/platform/macos/root.zig#L1766-L1767】. Equivalent `configureShortcuts` functions exist for Windows (`src/platform/windows/root.zig`) and Linux (`src/platform/linux/root.zig`).

### How the Runtime Delivers Events

The full flow from declaration to handler follows four discrete steps:

1. **Manifest → AppInfo** – `src/tooling/manifest.zig` parses and validates shortcuts from `app.zon`.
2. **Runtime → Platform** – `platform.services.configureShortcuts` registers the validated shortcuts with the OS.
3. **OS → Runtime** – When the user presses a registered key combo, the OS sends a shortcut event back to the runtime.
4. **Runtime → App** – If the shortcut maps to a command, the runtime invokes `Options.on_command`. If the key is a chrome shortcut, the runtime invokes `Options.on_key` only after the widget tree declines the event.

## Complete Native SDK Shortcut Example

Here is a consolidated implementation showing manifest declarations, app scaffolding, and both callback types:

```zig
// 1️⃣ app.zon – declare shortcuts
shortcuts: [
  { id = "command.toggle", key = "t", modifiers = [ "command" ] },
  { id = "command.clear",  key = "escape" },
]

// 2️⃣ UI app definition
pub fn MyApp(comptime Model: type, comptime Msg: type) type {
    return UiAppWithFeatures(Model, Msg, .{});
}

// 3️⃣ Options wiring
const options = UiApp.Options{
    .name = "Demo",
    .scene = ...,
    .canvas_label = "main",
    .on_command = onCommand,
    .on_key = onKey,
};

fn onCommand(name: []const u8) ?Msg {
    return switch (name) {
        "command.toggle" => Msg{ .Toggle = {} },
        "command.clear"  => Msg{ .Clear = {} },
        else => null,
    };
}

fn onKey(event: canvas.WidgetKeyboardEvent) ?Msg {
    // Chrome shortcuts (space, play/pause) arrive here.
    return switch (event.key) {
        "space" => Msg{ .PlayPause = {} },
        else    => null,
    };
}

```

Several example applications under `examples/*/src/runner.zig` demonstrate this pattern in practice. The `calculator` example, for instance, allocates a `shortcuts` buffer and uses a Chrome shortcut for Escape.

## Summary

- Define shortcuts in `app.zon` using the `native_sdk.Shortcut` schema declared in `src/platform/types.zig` lines 82–99.
- The manifest parser in `src/tooling/manifest.zig` validates shortcuts via `validateShortcut` and stores them in `AppInfo.shortcuts`.
- Standard keyboard shortcuts are handled by `UiApp.Options.on_command` and receive the manifest `id` as a `[]const u8`.
- Chrome shortcuts use `UiApp.Options.on_key` with a `canvas.WidgetKeyboardEvent` and only fire after the widget hierarchy declines the key.
- Platforms register shortcuts via `configureShortcuts` in `src/platform/macos/root.zig`, `src/platform/windows/root.zig`, and `src/platform/linux/root.zig`.

## Frequently Asked Questions

### What is the difference between keyboard shortcuts and chrome shortcuts in the Native SDK?

Keyboard shortcuts are global command shortcuts routed through `on_command` using the command `id` from the manifest. Chrome shortcuts are platform-specific window-level events, such as play/pause or back gestures, that bypass the widget hierarchy and are delivered through `on_key` only after the widget tree declines the event.

### How do I declare modifier keys for a shortcut in app.zon?

You add the `modifiers` array to the shortcut entry in `app.zon`. For example, `{ id = "command.palette", key = "k", modifiers = [ "command" ] }` registers `cmd+K`. The parser in `src/tooling/manifest.zig` validates the entry, and the `ShortcutModifiers` default in `src/platform/types.zig` ensures at least one modifier is present for character keys.

### Why does my spacebar shortcut use on_key instead of on_command?

The Native SDK marks non-character keys like `space` as **chrome shortcuts** to prevent them from intercepting text input. Because these shortcuts must not steal focus from typing widgets, the runtime routes them through `on_key` after the widget tree has had a chance to consume the event.

### Where does the OS registration happen at startup?

The runtime calls `platform.services.configureShortcuts` during initialization. On macOS this is implemented in `src/platform/macos/root.zig` around lines 1766–1767, with equivalent routines in `src/platform/windows/root.zig` and `src/platform/linux/root.zig`. Each platform translates the `[]const platform.Shortcut` slice into native OS APIs such as `NSMenu`.