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’slabel,width,height,titlebarstyle, and its array ofShellViews. Defined insrc/primitives/app_manifest/types.zig.ShellConfig— The top-level container that exposes thewindowsarray parsed from the manifest. Same file as above.Runtime.createShellWindow— Consumes aShellWindowrecord and builds the underlyingplatform.WindowInfo. Located insrc/runtime/window_views.zig.Runtime.applyShellViews— Maps theShellViewarray into native canvas layers or webviews after the window exists. Same file as above.ShellLayout— Translates layout hints (x,y,edge,width,height) into concreteRectFbounds. Found insrc/runtime/shell_layout.zig.WindowStorageMethods.updateWindowState— Persists geometry and visibility across launches. Defined insrc/window_state/root.zig.
Runtime Lifecycle
- Manifest parsing. On startup, the SDK loads
app.zonand reads theShellConfig.windowsslice. - Startup window. The first element of the
windowsarray becomes the host window created before the scene loads. - Model-declared windows. After the scene loads, the runtime invokes
Runtime.createShellWindowfor each remainingShellWindowrecord. - View creation. The runtime validates the view hierarchy and instantiates native views according to each
ShellViewkind. - State persistence. Resizes, moves, and close events are forwarded to
WindowStorageMethods.updateWindowStateso 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
labeluniquely identifies the window for effects and programmatic commands. titlebarcontrols the OS chrome and accepts values such as.standardor.hidden.- Each
ShellViewdeclares itskind(canvas,webview,image, etc.) and optional layout hints thatShellLayoutresolves 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
ShellWindowrecords inapp.zondescribe windows declaratively, including size, position, title-bar style, and view hierarchy.- The first window in the
ShellConfig.windowsarray is treated as the startup window; subsequent entries are created viaRuntime.createShellWindow. - Views are wired automatically by the runtime using the
ShellViewkind and layout hints resolved throughShellLayout. - Pure-canvas windows can use
createSourcelessShellWindowto bypass webview creation entirely. - Window state is transparently persisted through
WindowStorageMethods.updateWindowStateinsrc/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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →