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 versionTwo things the CLI handles for you, so you never configure them:
- The SDK location. Apps build against the SDK the CLI ships with —
native initrecords the path automatically (override with--framework <sdk path>). - The Zig toolchain.
native dev|build|testuse the Zig on your PATH when its version is compatible, and otherwise offer to download the pinned version into~/.native/toolchains/(checksum-verified; pass--yesto skip the prompt in scripts).
Create an app
native init my_app
cd my_appThis 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.
| File | Purpose |
|---|---|
src/app.native | The entire UI: elements, layout, bindings, and message dispatch |
src/main.zig | The logic: Model, Msg, update, and the window scene |
src/tests.zig | Full-loop UI tests that click buttons through typed dispatch — no GUI needed |
app.zon | App manifest: identity, window and view declarations, permissions, security policy |
assets/icon.png | The app icon source: one square image packaging turns into every platform's icon artifacts |
.gitignore, README.md | Ignores 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 devThe 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 checknative 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.zonMarkup 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 testThe 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 buildThis 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