# Quickstart

> Create an app with native init, run native dev, edit .native markup and Zig Model/Msg/update, and verify the first live window.

- Repository: vercel-labs/native
- GitHub: https://github.com/vercel-labs/native
- Human docs: https://grok-wiki.com/public/docs/vercel-labs-native-8ccc3580636a
- Complete Markdown: https://grok-wiki.com/public/docs/vercel-labs-native-8ccc3580636a/llms-full.txt

## Source Files

- `README.md`
- `docs/src/app/quick-start/layout.tsx`
- `examples/hello/build.zig`
- `examples/calculator/README.md`
- `src/tooling/dev.zig`

---

---
title: "Quickstart"
description: "Create an app with native init, run native dev, edit .native markup and Zig Model/Msg/update, and verify the first live window."
---

`native init` scaffolds a zero-config native-rendered app (`app.zon` + `src/` + `assets/`), and `native dev` builds a Debug binary, launches a real OS window, and arms markup hot reload for `src/app.native`. Logic stays in Zig as `Model`, `Msg`, and `update` on `native_sdk.UiApp`; the view is declarative markup that binds and dispatches but never mutates state.

## Prerequisites

| Requirement | Notes |
| --- | --- |
| Host OS | macOS 11+ (primary), Linux, or Windows |
| CLI | `@native-sdk/cli` on npm, binary name `native` |
| Zig | Optional on PATH; CLI resolves or offers the pinned toolchain (`0.16.0`) under `~/.native/toolchains/` |
| Display | A desktop session for `native dev` (headless machines use `native test` / `-Dplatform=null`) |

Install and confirm the binary:

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

Expected shape: `native 0.4.0 (commit …, automation protocol v…)`.

<Note>
The CLI records the framework path at init (the SDK tree shipped with the npm package, or a checkout that contains `src/root.zig`). Override with `native init … --framework <sdk path>` or set `NATIVE_SDK_PATH` when running verbs later.
</Note>

## Create an app

```bash
native init my_app
cd my_app
```

Flags:

| Flag | Values | Default | Effect |
| --- | --- | --- | --- |
| path | directory | `.` | Destination root for the scaffold |
| `--frontend` | `native`, `next`, `vite`, `react`, `svelte`, `vue` | `native` | Native markup app vs WebView frontend shell |
| `--framework` | filesystem path | resolved from the `native` binary | SDK root written into full scaffolds / used by the managed graph |
| `--full` | boolean | off | Own `build.zig` / `build.zig.zon`, editor settings, CI (native frontend only; web frontends always scaffold full) |

Success output:

```text
created Native SDK app at my_app (native)

Next steps:
  cd my_app
  native dev
```

### Slim scaffold (default, `--frontend native`)

No app-owned build files. Verbs synthesize the graph under `.native/build/` (gitignored) and install binaries to `zig-out/`.

:::files
my_app/
├── app.zon              # identity, shell window, permissions, security
├── assets/
│   └── icon.png         # packaging icon source
├── src/
│   ├── app.native       # entire UI (markup)
│   ├── main.zig         # Model, Msg, update, UiApp wiring
│   └── tests.zig        # full-loop headless UI tests
├── .gitignore           # .native/, zig-out/, .zig-cache/
└── README.md
:::

### Full scaffold (`native init my_app --full`)

Same app sources plus owned build and tooling:

| Extra path | Purpose |
| --- | --- |
| `build.zig` | Calls `native_sdk.addApp` |
| `build.zig.zon` | Path dependency on the framework |
| `.vscode/settings.json` | Associates `*.native` with HTML mode |
| `.github/workflows/ci.yml` | Null-platform test + Linux smoke pattern |

Web frontends (`--frontend next|vite|react|svelte|vue`) always write the expanded shape plus a `frontend/` package and wire `native dev` to the frontend server (see [Frontend projects](/frontend)).

## Run the first window

```bash
native dev
```

What runs:

1. Resolve Zig (`NATIVE_SDK_ZIG` → compatible PATH `zig` → managed `~/.native/toolchains/` → optional download with consent, or `--yes`).
2. If no `build.zig`, generate/refresh `.native/build/` against the framework root.
3. `zig build run -Doptimize=Debug` from the app root (or `--build-file` + `--prefix` for the managed graph).
4. Launch the app process group; Ctrl-C tears down the tree so automation-enabled orphans do not linger.

Console signal:

```text
native dev: building and running my-app (Debug) — hot reload arms in Debug builds
```

The first compile builds the app and the SDK; later runs are incremental. A window opens with the scaffolded counter (480×320, GPU surface canvas `main-canvas`).

<Check>
Click **+** / **−** / **Reset**. The center label and status bar update. That is live `Msg` dispatch through the engine, not a static mock.
</Check>

### Name and binary

`native init my_app` derives package identity in `app.zon` (for example `.name = "my-app"`). Release and install paths use that name:

```text
zig-out/bin/my-app
```

## Authoring surface

### Markup: `src/app.native`

Scaffold view (bindings and message tags only — no mutation):

```html
<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>
```

| Construct | Role |
| --- | --- |
| `{count}` | Read-only binding to `Model.count` |
| `on-press="increment"` | Dispatches `Msg` tag `increment` |
| layout attrs (`gap`, `padding`, `main`, `cross`, `grow`) | Flex layout on engine widgets |

### Logic: `src/main.zig`

Core loop written by the scaffold:

```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,
    }
}
```

Wiring (same file):

- `pub const app_markup = @embedFile("app.native");`
- `const CounterApp = native_sdk.UiApp(Model, Msg);`
- `CounterApp.create(…, .{ .update = update, .markup = .{ .source = app_markup, .watch_path = "src/app.native", .io = init.io }, … })`
- `runner.runWithOptions(app_state.app(), …)`

`update` is the only place model state changes. Markup rebuilds from the model after each message.

```text
  pointer / keyboard / button
            │
            ▼
         Msg tag
            │
            ▼
   update(*Model, Msg)     ◄── only mutation site
            │
            ▼
   rebuild view from Model
            │
            ▼
   GPU surface / OS window
```

## Edit while it runs

With `native dev` (Debug) and `.markup.watch_path = "src/app.native"`:

1. Change a label or add a button in `src/app.native`.
2. Save. Within about two seconds the window repaints.
3. Model state (for example `count`) is preserved across reload.
4. Parse failures keep the last good view on screen and surface line/column diagnostics (`app_state.markup_diagnostic`).

<Warning>
Hot reload is a Debug-path feature. `native build` defaults to ReleaseFast and does not arm the watcher the same way. Pass an explicit `-Doptimize=…` to `native dev` only if you intentionally override the Debug default (reload will not arm in release-mode binaries).
</Warning>

Zig logic changes (`Model` / `Msg` / `update`) require a rebuild — stop and re-run `native dev` (or keep the process cycle short).

## Validate without a full GUI session

### `native check`

```bash
native check
# optional: native check --strict
```

Collects every `src/**/*.native`, validates markup, validates `app.zon`, and uses `zig-out/model-contract.zon` when present (produced by a successful `native test` / model-contract step) for binding and message-tag checks against real `Model`/`Msg`.

Success:

```text
checked 1 markup file and app.zon
```

(or `… against the model contract` when the contract artifact is fresh).

### `native test`

```bash
native test
```

Runs the scaffolded suite in `src/tests.zig`: builds the markup tree, finds **+** / **−** / **Reset** by text, dispatches `msgForPointer` like the runtime, asserts model values and stable widget ids — headless, no display server.

Success ends with a build summary and:

```text
native test: passed (test tally in the build summary above)
```

Null platform (CI / headless hosts):

```bash
native test -Dplatform=null
```

## Release binary

```bash
native build
```

Default optimize: ReleaseFast. Install location:

```text
native build: built zig-out/bin/my-app (ReleaseFast)
```

Package into a host app bundle with packaging tooling after the binary exists (see [Packaging](/packaging)).

## Own the build later

| Path | When |
| --- | --- |
| `native init … --full` | Want `build.zig` from day one |
| `native eject` | Zero-config app outgrew the managed graph; writes owned `build.zig` / `build.zig.zon` once, then verbs drive `zig build` without regenerating them |

After eject, `native dev|build|test` keep working against your files.

## Verification checklist

| Signal | How to confirm |
| --- | --- |
| CLI installed | `native version` prints version + commit |
| Scaffold created | `created Native SDK app at …` and `src/app.native` exists |
| Dev build | `native dev: building and running … (Debug)` |
| Live window | OS window titled from `display_name` / shell config; counter UI visible |
| Interaction | +/-/Reset change `{count}` and status bar |
| Hot reload | Edit `app.native` → repaint without losing count |
| Markup valid | `native check` → checked N markup files and `app.zon` |
| Tests green | `native test` → passed |
| Release artifact | `native build` → `zig-out/bin/<name>` |

## Common failures

| Symptom | Cause | Fix |
| --- | --- | --- |
| `no app.zon here` | Wrong cwd | `cd` to the app root or `native dev path/to/app` |
| Framework not found | CLI cannot locate SDK | Reinstall `@native-sdk/cli`, run a checkout-built `zig-out/bin/native`, set `NATIVE_SDK_PATH`, or re-init with `--framework` |
| Zig missing / wrong version | No compatible toolchain | Accept the managed download, pass `--yes`, or set `NATIVE_SDK_ZIG` |
| Hot reload never fires | Release optimize, or no `watch_path` | Use default `native dev` Debug; keep scaffold markup options |
| Test / check mismatch after UI edit | `tests.zig` still expects old button labels | Update tests or restore labels |
| `native init --help` must not scaffold | Flag discipline | `--help` exits 0 without writing files |

## Optional next shapes

| Goal | Command / example |
| --- | --- |
| Web frontend in a WebView | `native init my_web --frontend next` (or vite/react/svelte/vue) |
| Smallest in-repo zero-config app | `examples/habits` (`app.zon` + `src/`, same verb shape) |
| Precision markup + plain TEA update | `examples/calculator` (`native dev`, `native test -Dplatform=null`) |
| Agent skill packs | `native skills list` → [Agent skills](/agent-skills) |

`examples/hello` is a hand-wired WebView shell (`zig build run`), not the default `native init` counter path.

## Next

<CardGroup>
  <Card title="Installation" href="/installation">
    Platform packages, prerequisites, and binary success signals per host OS.
  </Card>
  <Card title="App model" href="/app-model">
    Model, Msg, update loop wiring, and hot reload boundaries.
  </Card>
  <Card title="Native UI markup" href="/native-ui">
    Elements, attributes, flex layout, bindings, and LSP diagnostics.
  </Card>
  <Card title="State and data flow" href="/state-data-flow">
    Derive-don't-store, dispatch paths, and effects as the side-effect channel.
  </Card>
  <Card title="Testing" href="/testing">
    Full-loop UI tests, headless truth drivers, and frame-level checks.
  </Card>
  <Card title="CLI reference" href="/cli-reference">
    init, dev, check, test, build, eject, doctor, and flag surface.
  </Card>
</CardGroup>
