docs/guide/getting-started
Get started · 5 min

Quick start

Create a native macOS app that lives outside the zenit repo and builds and runs on its own.

8 min read
Building with Claude Code or another agent? Install the zenit UI dev Skill first.

It packs this manual, the component decision table and real-project pitfalls into instructions your agent loads on demand, so the code it writes follows zenit conventions.

Prerequisites

This release targets macOS, with Apple Silicon continuously verified. Make sure you have Zig 0.15.2 and the Xcode Command Line Tools; Bun is only needed for end-to-end tests.

Terminal
zig version
xcode-select -p

Create a project

Start from templates/minimal-app in the repo. It is a complete downstream project:

templates/minimal-app
minimal-app/
├── README.md
├── build.zig          # zenit.attach() + run / app steps
├── build.zig.zon      # declares the zenit dependency
├── e2e/
│   └── record-demo.ts # drives and records the app through the Harness
└── src/
    └── main.zig       # counter-button app
  1. 1
    Copy the template

    Copy templates/minimal-app to a new directory outside the zenit repo.

  2. 2
    Fix the dependency path

    The template defaults to .path = "../../", which only resolves inside the repo. After copying, point it at your zenit checkout relative to build.zig.zon, or pin a published revision with zig fetch --save=zenit <url>.

  3. 3
    Generate a fingerprint

    Don’t keep the template’s .fingerprint: two projects sharing it collide in Zig’s package cache. Delete the line, run zig build once, and paste back the value from the error. Rename .name = .myapp while you’re there.

Terminal
git clone https://github.com/version-next/zenit.git
cp -R zenit/templates/minimal-app ./myapp
cd myapp   # then set .zenit = .{ .path = "../zenit" } in build.zig.zon
zig build

Wire up zenit

build.zig.zon declares the dependency; build.zig gets zenit’s own build API through @import("zenit").

build.zig.zon
.{
    .name = .myapp,
    .version = "0.1.0",
    .fingerprint = 0x8798022a7f8e220e, // replace: delete this line, run zig build once
    .minimum_zig_version = "0.15.2",

    .dependencies = .{
        // Relative to this file. Absolute paths are rejected by Zig 0.15.2.
        .zenit = .{ .path = "../zenit" },
    },

    .paths = .{
        "README.md",
        "build.zig",
        "build.zig.zon",
        "e2e",
        "src",
    },
}

Here is the template’s build.zig verbatim. It’s more than one attach call: it forwards the test switches, installs the Harness client, and defines the run step and .app packaging:

CallWhat it does
zenit.attach(zenit_dep, exe)Adds the ui and zenit_app module imports, compiles the five macOS ObjC bridges and links Cocoa, Metal, CoreText and the other frameworks
.@"test-mode" / .@"e2e-port"Dependency options are isolated in Zig; forward them explicitly so zig build -Dtest-mode=true reaches zenit
zenit.installHarnessClient(b, zenit_dep)Installs the typed Harness client to zig-out/share/zenit/harness/client.ts for e2e/record-demo.ts
zenit.bundleApp(b, .{ ... })Produces a double-clickable zig-out/<display_name>.app, ad-hoc signed here

Mount your first UI tree

App.runWith creates the root Scope, calls your mount function once and takes over the event loop. The mount function must have the signature fn (cx: *ui.Cx, scope: *ui.Scope) anyerror!*ui.Node; your app only returns the root node.

cx.bindState hands Counter to the Cx, and cx.on turns one of its methods into the button callback; test_id lets E2E scripts find the button by meaning.

Build and run

Terminal
zig build run   # build and run the unbundled executable
zig build app   # package zig-out/My App.app (macOS)
The real Hello Button.app launched from build output (examples/hello_button, the same counter button as the template): the harness clicks three times with the hand cursor and the label reads “Clicked 3 times”. Window, widget and response all come from the public API above.

The template also ships a recording script: build with zig build -Dtest-mode=true, start the app, then run e2e/record-demo.ts to get the same kind of clip. See E2E harness.

zenit · Dual-licensedFree for open-source projects under GPL-3.0-only; closed-source or commercial products need a commercial license.Contact the author: zongyi.xzy#gmail.com (replace # with @)zenit 4c50245 2026-10-01