---
title: "Démarrage rapide — Docs zenit Zig UI"
description: "Créez une app macOS native qui vit hors du dépôt zenit et se compile et s'exécute de façon autonome."
url: https://zenit.z.express/fr/docs/guide/getting-started
language: fr
alternate_en: https://zenit.z.express/docs/guide/getting-started.md
alternate_zh: https://zenit.z.express/zh/docs/guide/getting-started.md
alternate_es: https://zenit.z.express/es/docs/guide/getting-started.md
alternate_ja: https://zenit.z.express/ja/docs/guide/getting-started.md
alternate_ko: https://zenit.z.express/ko/docs/guide/getting-started.md
alternate_de: https://zenit.z.express/de/docs/guide/getting-started.md
project: zenit v0.1.0-alpha (Zig 0.15.2, macOS)
source: https://github.com/version-next/zenit
---

# Démarrage rapide

Créez une app macOS native qui vit hors du dépôt zenit et se compile et s'exécute de façon autonome.

Vous développez avec Claude Code ou un autre agent ? Installez d’abord la zenit UI dev Skill.

Elle regroupe ce manuel, la table de choix des composants et les pièges de projets réels en instructions que votre agent charge à la demande, pour que le code qu’il écrit suive les conventions de zenit.

[Télécharger la Skill.zip · 54 KB](https://zenit.z.express/downloads/zenit-ui-dev.zip)[Guide d’installation](https://zenit.z.express/fr/docs/guide/ai-skill)

## Prérequis

Cette version cible macOS, avec Apple Silicon vérifié en continu. Assurez-vous d'avoir Zig 0.15.2 et les Xcode Command Line Tools ; Bun n'est nécessaire que pour les tests de bout en bout.

```sh
zig version
xcode-select -p
```

> WARNING
> 
> **Les versions doivent correspondre.** Le template déclare `minimum_zig_version = "0.15.2"` dans `build.zig.zon` ; d'autres versions peuvent échouer de façon déroutante sur les API de build ou les empreintes de paquet.

## Créer un projet

Partez de `templates/minimal-app` dans le dépôt. C'est un projet downstream complet :

`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.  **Copier le template**
    
    Copiez templates/minimal-app dans un nouveau répertoire hors du dépôt zenit.
    
2.  **Corriger le chemin de dépendance**
    
    Le template utilise par défaut `.path = "../../"`, qui ne se résout qu'à l'intérieur du dépôt. Après la copie, pointez-le vers votre checkout de zenit relativement à build.zig.zon, ou épinglez une révision publiée avec `zig fetch --save=zenit <url>`.
    
3.  **Générer une empreinte**
    
    Ne gardez pas le .fingerprint du template : deux projets qui le partagent entrent en collision dans le cache de paquets de Zig. Supprimez la ligne, lancez zig build une fois et recollez la valeur indiquée par l'erreur. Renommez .name = .myapp au passage.
    

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

## Brancher zenit

`build.zig.zon` déclare la dépendance ; `build.zig` obtient l'API de build de zenit via `@import("zenit")`.

`build.zig.zon`

```zig
.{
    .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",
    },
}
```

Voici le build.zig du template tel quel. Il fait plus qu'un appel à attach : il transmet les options de test, installe le client Harness et définit l'étape run et le packaging .app :

`build.zig`

```zig
const std = @import("std");
const zenit = @import("zenit");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});
    const test_mode = b.option(bool, "test-mode", "Enable the Zenit automation harness") orelse false;
    const e2e_port = b.option(u16, "e2e-port", "Zenit Harness RPC directory suffix") orelse 19816;

    const zenit_dep = b.dependency("zenit", .{
        .target = target,
        .optimize = optimize,
        // Dependency build options are isolated in Zig. Forward these
        // explicitly so `zig build -Dtest-mode=true` reaches Zenit.
        .@"test-mode" = test_mode,
        .@"e2e-port" = e2e_port,
    });

    const exe = b.addExecutable(.{
        .name = "myapp",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
        }),
    });

    zenit.attach(zenit_dep, exe);
    zenit.installHarnessClient(b, zenit_dep);

    b.installArtifact(exe);

    const run_cmd = b.addRunArtifact(exe);
    run_cmd.step.dependOn(b.getInstallStep());
    const run_step = b.step("run", "Run the app");
    run_step.dependOn(&run_cmd.step);

    // Optional: `zig build app` packages a double-clickable .app bundle.
    if (target.result.os.tag == .macos) {
        const bundled = zenit.bundleApp(b, .{
            .exe = exe,
            .display_name = "My App",
            .bundle_id = "com.example.myapp",
            .version = "0.1.0",
            .signing = .ad_hoc,
        });
        const app_step = b.step("app", "Build the .app bundle");
        app_step.dependOn(bundled.final_step);
    }
}
```

| Appel | Rôle |
| --- | --- |
| `zenit.attach(zenit_dep, exe)` | Ajoute les imports de modules ui et zenit\_app, compile les cinq ponts ObjC macOS et lie Cocoa, Metal, CoreText et les autres frameworks |
| `.@"test-mode" / .@"e2e-port"` | Les options de dépendance sont isolées en Zig ; transmettez-les explicitement pour que zig build -Dtest-mode=true atteigne zenit |
| `zenit.installHarnessClient(b, zenit_dep)` | Installe le client Harness typé dans zig-out/share/zenit/harness/client.ts pour e2e/record-demo.ts |
| `zenit.bundleApp(b, .{ ... })` | Produit un zig-out/<display\_name>.app lançable d'un double-clic, ici signé ad hoc |

## Monter votre premier arbre UI

`App.runWith` crée le Scope racine, appelle une fois votre fonction de montage et prend la main sur la boucle d'événements. La fonction de montage doit avoir la signature `fn (cx: *ui.Cx, scope: *ui.Scope) anyerror!*ui.Node` ; votre app se contente de renvoyer le nœud racine.

`src/main.zig`

```zig
const std = @import("std");
const ui = @import("ui");
const App = @import("zenit_app").App;

const Counter = struct {
    n: u32 = 0,
    label: ?*ui.Node = null,
    buf: [32]u8 = undefined,

    pub fn increment(self: *Counter) void {
        self.n += 1;
        const node = self.label orelse return;
        const content = std.fmt.bufPrint(&self.buf, "Clicked {d} times", .{self.n}) catch return;
        if (node.getText()) |old| {
            var t = old;
            t.content = content;
            node.setText(t);
        }
        node.markRenderDirty();
    }
};

fn mountUi(cx: *ui.Cx, scope: *ui.Scope) anyerror!*ui.Node {
    const allocator = cx.allocator;

    const counter = try cx.bindState(Counter, .{});
    const click = cx.on(Counter, counter, Counter.increment);

    const root = try ui.box(cx, .{
        .width = .fill(),
        .height = .fill(),
        .direction = .column,
        .gap = 16,
        .padding = ui.Padding.all(40),
        .background = cx.tokens.color.bg_primary,
        .align_items = .center,
        .justify = .center,
    }, .{});

    try root.appendChild(allocator, try ui.text(cx, "Hello, zenit!", .{
        .font_size = 24,
        .font_weight = 600,
        .color = cx.tokens.color.fg_primary,
    }));

    const button = try ui.widgets.Button(.{
        .label = "Click me",
        .variant = .primary,
        .on_click = click,
    }).mount(scope, cx);
    button.meta.ownership.meta.test_id = "counter.increment";
    try root.appendChild(allocator, button);

    const label = try ui.text(cx, "Clicked 0 times", .{
        .font_size = 14,
        .color = cx.tokens.color.fg_secondary,
    });
    try root.appendChild(allocator, label);
    counter.label = label;

    return root;
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    const app = try App.init(gpa.allocator(), .{
        .window = .{ .width = 640, .height = 480, .title = "Hello, zenit" },
    });
    defer app.deinit();

    try app.runWith(mountUi);
}
```

`cx.bindState` confie `Counter` au Cx, et `cx.on` transforme l'une de ses méthodes en callback du bouton ; `test_id` permet aux scripts E2E de trouver le bouton par son rôle.

> NOTE
> 
> **Le template met à jour le texte à la main pour rester minimal.** Le buffer vit dans `Counter` à une adresse stable, donc le prêter à `setText` est sûr. Les vraies apps gardent plutôt le compteur dans un Signal et laissent `ui.textFmt` mettre à jour le libellé — voir [Réactivité](https://zenit.z.express/fr/docs/guide/reactivity).

## Compiler et exécuter

```sh
zig build run   # build and run the unbundled executable
zig build app   # package zig-out/My App.app (macOS)
```

[Video](https://zenit.z.express/media/hello-button.mp4?v=b4d2b8f8a0)

La vraie Hello Button.app lancée depuis la sortie du build (examples/hello\_button, le même bouton compteur que le template) : le harness clique trois fois avec le curseur main et le libellé affiche « Clicked 3 times ». Fenêtre, widget et réponse viennent tous de l'API publique ci-dessus.

Le template fournit aussi un script d'enregistrement : compilez avec `zig build -Dtest-mode=true`, lancez l'app, puis exécutez `e2e/record-demo.ts` pour obtenir le même type de clip — voir [E2E harness](https://zenit.z.express/fr/docs/advanced/e2e).

> TIP
> 
> **Ensuite.** Une fois la fenêtre affichée, lisez ensuite Structure du projet et l'arbre UI. Ne vous précipitez pas pour bâtir votre propre couche de composants : zenit fournit déjà widgets, thèmes et primitives réactives.
