How to Implement Multi-Window Apps with Model-Declared Windows in the Native SDK

You implement multi-window apps with model-declared windows in the Native SDK by declaring ShellWindow records in your app.zon manifest, which the runtime reads to create native platform windows and wire their ShellView hierarchies automatically.

The vercel-labs/native repository provides a declarative multi-window API that lets you describe every window in your application’s manifest. By modeling windows as data rather than platform-specific code, you can define multiple independent windows with custom sizes, title-bar styles, and view layouts that persist across app launches.

Declarative Multi-Window Architecture in the Native SDK

The Native SDK treats every application window as a ShellWindow struct that lives in app.zon. Because the window model is pure data, the runtime can create platform-native windows, lay out their views, and restore their state without imperative UI code.

Core Components

  • ShellWindow — Declares the window’s label, width, height, titlebar style, and its array of ShellViews. Defined in src/primitives/app_manifest/types.zig.
  • ShellConfig — The top-level container that exposes the windows array parsed from the manifest. Same file as above.
  • Runtime.createShellWindow — Consumes a ShellWindow record and builds the underlying platform.WindowInfo. Located in src/runtime/window_views.zig.
  • Runtime.applyShellViews — Maps the ShellView array into native canvas layers or webviews after the window exists. Same file as above.
  • ShellLayout — Translates layout hints (x, y, edge, width, height) into concrete RectF bounds. Found in src/runtime/shell_layout.zig.
  • WindowStorageMethods.updateWindowState — Persists geometry and visibility across launches. Defined in src/window_state/root.zig.

Runtime Lifecycle

  1. Manifest parsing. On startup, the SDK loads app.zon and reads the ShellConfig.windows slice.
  2. Startup window. The first element of the windows array becomes the host window created before the scene loads.
  3. Model-declared windows. After the scene loads, the runtime invokes Runtime.createShellWindow for each remaining ShellWindow record.
  4. View creation. The runtime validates the view hierarchy and instantiates native views according to each ShellView kind.
  5. State persistence. Resizes, moves, and close events are forwarded to WindowStorageMethods.updateWindowState so the next launch restores the exact layout.

Declaring Windows in app.zon

Window declarations live inside the .shell block of your manifest. Each window carries a unique label, dimensions, title-bar configuration, and a list of views.

.shell = .{
  windows = .{
    .{
      label = "main",
      width = 720,
      height = 480,
      titlebar = .standard,
      views = &.{
        .{ label = "canvas", kind = .canvas },
        .{ label = "sidebar", kind = .canvas, edge = .right, width = 200 },
      },
    },
    .{
      label = "settings",
      width = 500,
      height = 400,
      title = "Settings",
      titlebar = .standard,
      views = &.{
        .{ label = "web", kind = .webview, url = "https://example.com/settings" },
      },
    },
  },
};

  • The label uniquely identifies the window for effects and programmatic commands.
  • titlebar controls the OS chrome and accepts values such as .standard or .hidden.
  • Each ShellView declares its kind (canvas, webview, image, etc.) and optional layout hints that ShellLayout resolves at runtime.

Runtime Window Creation for Multi-Window Apps

Windows are not just static manifest entries; you can construct and open them dynamically from Zig using the same ShellWindow model.

Opening a Secondary Window Programmatically

The public API entry point is Runtime.createShellWindow, which mirrors the manifest record. When the window’s views already declare their own content—for example a webview with a URL—you pass null for the initial WebViewSource.

const native_sdk = @import("native_sdk");

fn openSettings(runtime: *native_sdk.Runtime) !void {
    const settings_win = native_sdk.ShellWindow{
        .label = "settings",
        .title = "App Settings",
        .width = 500,
        .height = 400,
        .views = &.{ .{
            .label = "web",
            .kind = .webview,
            .url = "https://example.com/settings",
        } },
    };

    _ = try runtime.createShellWindow(settings_win, null);
}

Under the hood, createShellWindow delegates to Runtime.createShellWindowWithSourcePolicy, which allocates the native platform window and then invokes the view-creation pipeline. For pure-canvas windows that never need a webview, the SDK exposes createSourcelessShellWindow instead.

Targeting and Closing Windows by Label

To close a specific window from model code, use effectsWindowIdByLabel to resolve the manifest label to a live WindowId, then call closeWindow.

fn closeWindowByLabel(runtime: *native_sdk.Runtime, label: []const u8) bool {
    const win_id = native_sdk.effectsWindowIdByLabel(runtime, label) orelse return false;
    runtime.closeWindow(win_id) catch return false;
    return true;
}

This pattern is the canonical way to target model-declared windows from update loops or command handlers.

Window State Persistence in the Native SDK

The SDK automatically saves and restores window geometry. Whenever a window is resized, moved, or closed, the runtime calls WindowStorageMethods.updateWindowState in src/window_state/root.zig. On the next launch, the SDK reads the saved state and reapplies it, ensuring users see their previous layout rather than the default manifest values.

Real-World Example: The Notes Demo

The repository’s notes example demonstrates a single-window setup that you can extend to multiple windows. It declares a ShellConfig in Zig and assigns it to shell_scene.

const shell_windows = [_]native_sdk.ShellWindow{.{
    .label = "main",
    .title = "Notes",
    .width = 720,
    .height = 480,
    .views = &.{
        .{ .label = "canvas", .kind = .canvas },
        .{ .label = "toolbar", .kind = .canvas, .edge = .top, .height = 44 },
    },
}};
pub const shell_scene: native_sdk.ShellConfig = .{ .windows = &shell_windows };

You can find this declaration in examples/notes/src/main.zig. Expanding the shell_windows array with additional ShellWindow entries is all that is required to turn this into a multi-window app.

Summary

  • ShellWindow records in app.zon describe windows declaratively, including size, position, title-bar style, and view hierarchy.
  • The first window in the ShellConfig.windows array is treated as the startup window; subsequent entries are created via Runtime.createShellWindow.
  • Views are wired automatically by the runtime using the ShellView kind and layout hints resolved through ShellLayout.
  • Pure-canvas windows can use createSourcelessShellWindow to bypass webview creation entirely.
  • Window state is transparently persisted through WindowStorageMethods.updateWindowState in src/window_state/root.zig.

Frequently Asked Questions

Can I mix canvas-only windows and webview windows in the same app?

Yes. The manifest accepts any combination of canvas, webview, and other view kinds across multiple ShellWindow declarations. For windows that do not need a webview, the SDK provides createSourcelessShellWindow, which disables the webview layer and hosts only native canvas views.

How does the SDK decide which window opens first?

The runtime treats the first element of the ShellConfig.windows array as the startup window. The host creates this window before the scene finishes loading. All remaining windows in the array are instantiated afterward through the standard createShellWindow flow.

What layout properties are available for views inside a window?

Each ShellView supports layout hints such as x, y, width, height, and edge. The ShellLayout engine in src/runtime/shell_layout.zig converts these hints into concrete RectF bounds relative to the window frame, so you can build sidebars, toolbars, and centered canvases without manual geometry calculations.

How do I target a specific window from my Zig code?

Always use the window’s manifest label. The SDK exposes effectsWindowIdByLabel, which scans the live window table and returns the platform WindowId. Once you have the ID, you can call runtime methods such as closeWindow or send custom commands to that specific window.

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 →