5.7k

Quick Start

Native SDK is the complete toolkit for building beautiful native desktop applications: declarative markup, a predictable message-based state model, a modern component library, a native renderer, and tooling for building, running, and packaging apps — no browser or WebView in the binary. This page takes you from install to a running, tested app.

Prerequisites

  • macOS 11 or newer, Linux, or Windows

Get the CLI

npm install -g @native-sdk/cli
native version

Two things the CLI handles for you, so you never configure them:

  • The SDK location. Apps build against the SDK the CLI ships with — native init records the path automatically (override with --framework <sdk path>).
  • The Zig toolchain. native dev|build|test use the Zig on your PATH when its version is compatible, and otherwise offer to download the pinned version into ~/.native/toolchains/ (checksum-verified; pass --yes to skip the prompt in scripts).

Create an app

native init my_app
cd my_app

This scaffolds a native-rendered app — and nothing else. There are no build files to maintain: the CLI owns the build graph, generating it under .native/build/ (gitignored) on each run.

FilePurpose
src/app.nativeThe entire UI: elements, layout, bindings, and message dispatch
src/main.zigThe logic: Model, Msg, update, and the window scene
src/tests.zigFull-loop UI tests that click buttons through typed dispatch — no GUI needed
app.zonApp manifest: identity, window and view declarations, permissions, security policy
assets/icon.pngThe app icon source: one square image packaging turns into every platform's icon artifacts
.gitignore, README.mdIgnores for generated directories, and the commands on this page

Prefer to own build.zig from day one? native init my_app --full scaffolds the expanded shape (build files, editor config, a ready-made CI workflow) and you drive it with zig build directly.

Run it

native dev

The first run compiles the app and the SDK, so give it a minute; subsequent runs are incremental. A native window opens with a working counter. The whole view is src/app.native — a Native markup file:

<column gap="12" padding="16">
  <row gap="8" cross="center">
    <text grow="1">Counter</text>
    <button size="sm" variant="ghost" on-press="reset">Reset</button>
  </row>
  <row gap="8" main="center" cross="center" grow="1">
    <button variant="secondary" on-press="decrement">-</button>
    <text>{count}</text>
    <button variant="primary" on-press="increment">+</button>
  </row>
  <status-bar>count: {count}</status-bar>
</column>

The markup binds values ({count}) and dispatches messages (on-press="increment"); it can never mutate state. All logic lives in src/main.zig:

pub const Msg = union(enum) {
    increment,
    decrement,
    reset,
};

pub const Model = struct {
    count: i64 = 0,
};

pub fn update(model: *Model, msg: Msg) void {
    switch (msg) {
        .increment => model.count += 1,
        .decrement => model.count -= 1,
        .reset => model.count = 0,
    }
}

That is the whole loop: the model holds state, messages describe what happened, update is the only place state changes, and the view re-derives from the model. App Model walks through how the pieces connect.

Edit while it runs

src/app.native is embedded into the binary and watched while native dev runs — native dev runs a Debug build by default, which is what arms the hot-reload watcher. Edit it — change a label, add a button — and the window updates within a couple of seconds without losing the count. Parse failures keep the last good view on screen.

Check and test

native check

native check validates every .native file under src/ plus app.zon without building anything:

src/app.native: ok
info[manifest.valid]: app.zon is valid
checked 1 markup file and app.zon

Markup errors come back with file:line:column and a teaching message (native markup lsp provides the same diagnostics plus completion and hover in your editor). Then run the real tests:

native test

The generated src/tests.zig builds the real view from the markup, finds the + button, dispatches its press exactly like the runtime would, and asserts on the model and the rebuilt view — headless, on any machine. See Testing for the full tiers, including driving the live app from the outside with automation.

Build a release binary

native build

This produces an optimized binary and tells you where it landed:

built zig-out/bin/my-app (ReleaseFast)

(The binary name comes from app.zon: native init my_app sets .name = "my-app".) Where native dev runs a Debug build to arm hot reload, native build produces an optimized ReleaseFast binary. From there, Packaging turns it into a distributable app bundle with native package.

Escape hatch: own the build

If the app outgrows the managed graph — extra build steps, custom sources — run native eject once. It writes a build.zig/build.zig.zon you own into the app and never touches them again; native dev|build|test keep working, now driving your files through zig build. See the CLI reference.

Next steps

  • App Model — the model/message/update loop, wiring, and hot reload
  • Native UI — every element, attribute, and pattern in the markup
  • Components — the component catalog
  • State & Data Flow — derive-don't-store, bindings, and text editing state
  • Examples — complete apps in the repository, from a calculator to a native shell
  • Web Content — the secondary path for apps that embed an existing web frontend
  • Platform Support — what each host supports today