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

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:

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:

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】:

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.

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:

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:

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

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →