← Back to Blog

Zero-Native: Build Native Desktop Apps with Zig and Web UI (Vercel Labs)

Zero-Native: Build Native Desktop Apps with Zig and Web UI 🚀

What is it? Zero-native is an open-source (Apache 2.0) framework from Vercel Labs that lets you build native desktop applications using Zig as the native shell and your favorite web framework (Next.js, React, Svelte, Vue) as the UI layer. The result: tiny binaries, minimal memory usage, and instant rebuilds.

Why it's trending: Zero-native hit 3,600+ GitHub stars in just over a week because it solves a long-standing tension in desktop development. Electron apps are easy to build but bloated (200MB+ for a counter app). Native toolkits like SwiftUI or GTK produce small binaries but require learning platform-specific APIs and don't use web frontend skills. Zero-native splits the difference: your UI is the web stack you already know, while the native shell is a lean Zig binary that uses the system's built-in WebView (WKWebView on macOS, WebKitGTK on Linux) or an optional bundled Chromium via CEF.


📋 Prerequisites

Before we start, make sure you have:

  • Node.js 18+ and npm installed
  • Zig 0.14+ installed (ziglang.org/download)
  • A frontend framework preference: Next.js, React, Svelte, or Vue
  • macOS 11+, Linux, or Windows for building

🧠 Architecture Overview

Here's how the zero-native stack is structured:

Zero-Native Architecture

The architecture works in a clear top-to-bottom flow:

  1. Developer & CLI — You install the zero-native CLI via npm and run zero-native init my_app --frontend <framework>. The CLI generates a complete project with a Zig native shell and a frontend scaffold.

  2. app.zon — The app manifest declares metadata, icons, window config, web engine choice, security policies, and bridge permissions. The CLI generates it and reads it back at build time.

  3. Zig Native Shell — The generated shell contains four core components: the App object (describes your application), the Runtime (event loop + window management), WebViewSource (what to load — inline HTML, URL, or local assets), and the bridge handler for JS↔Zig communication.

  4. Web Frontend — Your framework of choice runs inside a WebView. Any modern web app works — you keep your existing dev tools, hot reload, and component libraries.

  5. Web Engine Layer — You choose: the System WebView (WKWebView on macOS, WebKitGTK on Linux) for minimal binary size, or Chromium via CEF for consistent rendering across platforms.

  6. Native Bridgewindow.zero.invoke() is the JavaScript-to-Zig bridge. Every call is size-limited, origin-checked, permission-checked, and routed only to registered handlers. The bridge gives your web UI access to native platform APIs.


🚀 Step-by-Step Setup

Step 1: Install the CLI

npm install -g zero-native

Verify the installation:

zero-native --help

You should see the CLI help output with init, build, and run commands.

Step 2: Create a New Project

zero-native init my-desktop-app --frontend svelte
cd my-desktop-app

This generates:

  • app.zon — App manifest
  • build.zig — Zig build configuration
  • src/ — Zig source files (main.zig, app.zig)
  • frontend/ — Your Svelte project scaffold

Step 3: Configure app.zon

Open app.zon and customize it:

.{
    .id = "com.example.my-desktop-app",
    .name = "my-desktop-app",
    .display_name = "My Desktop App",
    .version = "0.1.0",
    .web_engine = "system",
    .permissions = .{ "window" },
    .capabilities = .{ "webview", "js_bridge" },
    .security = .{
        .navigation = .{
            .allowed_origins = .{ "zero://app", "http://127.0.0.1:5173" },
        },
    },
    .windows = .{
        .{ .label = "main", .title = "My App", .width = 960, .height = 640 },
    },
}

Key fields explained:

  • web_engine"system" for the platform WebView, "chromium" for bundled Chromium/CEF
  • permissions — What the app can do ("window", "fs", "clipboard")
  • capabilities — Feature flags ("webview", "js_bridge", "automation")
  • security.navigation.allowed_origins — Which URLs the WebView can navigate to

Step 4: Add a Native Bridge Call

In your Svelte frontend (frontend/src/App.svelte):

<script>
  import { onMount } from "svelte";

  let bridgeStatus = $state("checking...");
  let message = $state("");

  onMount(async () => {
    if (window.zero) {
      bridgeStatus = "connected";
      const result = await window.zero.invoke("hello", { name: "World" });
      message = result;
    } else {
      bridgeStatus = "not available";
    }
  });
</script>

<main>
  <h1>Zero-Native Desktop App</h1>
  <p>Bridge: {bridgeStatus}</p>
  <p>Response: {message}</p>
</main>

Step 5: Build and Run

zig build run

The first build installs frontend dependencies, compiles the Zig shell, and opens a desktop window rendering your Svelte UI. The frontend dev server runs at http://127.0.0.1:5173 with hot reload — any change you make to the Svelte code refreshes immediately in the native window.

Step 6: Production Build

For a distributable package:

zig build package -- -Dpackage-target=macos

This produces a standalone .app bundle (or .exe for Windows, AppImage for Linux) in zig-out/. The binary size with the system WebView is under 1MB.


🐳 Docker-Based Development

You can also develop zero-native apps in a containerized environment:

# docker-compose.yml
version: "3.8"
services:
  dev:
    image: ziglang/zig:0.14.0
    volumes:
      - .:/app
    working_dir: /app
    ports:
      - "5173:5173"
    environment:
      - DISPLAY=:99
    command: >
      sh -c "zig build run"

Note: For GUI rendering inside Docker, you'll need an X11 socket or a VNC server. The system WebView requires a display server — for headless testing, use the Chromium/CEF backend with xvfb-run.


🔧 Config Reference

Setting Options Description
web_engine system, chromium Web rendering backend
permissions window, fs, clipboard, notifications Native API access
capabilities webview, js_bridge, automation Feature toggles
web_engine.system macOS: WKWebView, Linux: WebKitGTK, Windows: Edge WebView2 Platform default

✅ Verification Checklist

  • CLI installed: zero-native --help shows commands
  • New project created: zero-native init my_app --frontend svelte
  • app.zon contains valid app metadata
  • Frontend dev server starts: http://127.0.0.1:5173/
  • Native window opens with your UI rendered
  • Bridge works: window.zero.invoke() returns a response
  • Production package builds without errors
  • Binary size is under 2MB (system WebView)

🌟 Alternatives

Zero-native is not the only option for building native apps with web tech. Here's how it compares:

  • Electron / Tauri — Electron bundles Chromium (heavy, ~200MB); Tauri uses the system WebView and a Rust backend. Zero-native is like Tauri in concept but uses Zig instead of Rust, offering even smaller binaries and faster compile times.
  • Neutralino.js — Uses the system WebView with a Node.js backend. Zero-native replaces Node.js with Zig, giving you a native compilation target and no Node.js dependency in production.
  • Flutter (Desktop) — Dart-based, compiles to native code but requires learning Flutter's widget system. Zero-native keeps your existing web frontend skills intact.

Zero-native stands out when you want maximum binary efficiency, already know web frontend frameworks, and prefer Zig's simplicity over Rust's complexity.


📚 Resources

← Retour au Blog

Zero-Native : Créez des Apps Desktop Natives avec Zig et le Web UI (Vercel Labs)

Zero-Native : Créez des Apps Desktop Natives avec Zig et le Web UI 🚀

Qu'est-ce que c'est ? Zero-native est un framework open-source (Apache 2.0) développé par Vercel Labs qui permet de créer des applications desktop natives en utilisant Zig pour la couche système et votre framework web préféré (Next.js, React, Svelte, Vue) pour l'interface utilisateur. Résultat : des binaires minuscules, une mémoire minimale et des recompilations instantanées.

Pourquoi ça cartonne : Zero-native a atteint 3 600+ étoiles GitHub en un peu plus d'une semaine, car il résout un compromis qui dure depuis longtemps dans le développement desktop. Les apps Electron sont faciles à construire mais gonflées (200 Mo+ pour une app compteur). Les kits natifs comme SwiftUI ou GTK produisent des petits binaires mais nécessitent l'apprentissage d'API spécifiques à chaque plateforme et n'utilisent pas vos compétences web. Zero-native fait le grand écart : votre UI utilise la stack web que vous connaissez déjà, tandis que le shell natif est un binaire Zig léger qui utilise le WebView intégré du système (WKWebView sur macOS, WebKitGTK sur Linux) ou un Chromium optionnel via CEF.


📋 Prérequis

Avant de commencer, assurez-vous d'avoir :

  • Node.js 18+ et npm installés
  • Zig 0.14+ installé (ziglang.org/download)
  • Un framework frontend de votre choix : Next.js, React, Svelte ou Vue
  • macOS 11+, Linux ou Windows pour la compilation

🧠 Architecture

Voici comment la stack zero-native est structurée :

Architecture Zero-Native

L'architecture fonctionne en flux vertical du haut vers le bas :

  1. Développeur & CLI — Vous installez le CLI zero-native via npm et exécutez zero-native init my_app --frontend <framework>. Le CLI génère un projet complet avec un shell Zig natif et un squelette frontend.

  2. app.zon — Le manifeste de l'application déclare les métadonnées, icônes, configuration des fenêtres, choix du moteur web, politiques de sécurité et permissions du pont de communication. Le CLI le génère et le lit au moment de la compilation.

  3. Shell Zig Natif — Le shell généré contient quatre composants essentiels : l'objet App (description de l'application), le Runtime (boucle d'événements + gestion des fenêtres), WebViewSource (contenu à charger — HTML, URL ou ressources locales), et le gestionnaire de pont pour la communication JS↔Zig.

  4. Frontend Web — Votre framework préféré s'exécute dans une WebView. N'importe quelle application web moderne fonctionne — vous conservez vos outils de développement, le hot reload et vos bibliothèques de composants.

  5. Couche Moteur Web — Vous choisissez : le WebView Système (WKWebView sur macOS, WebKitGTK sur Linux) pour une taille de binaire minimale, ou Chromium via CEF pour un rendu cohérent entre plateformes.

  6. Pont Natifwindow.zero.invoke() est le pont JavaScript→Zig. Chaque appel est limité en taille, vérifié par origine, contrôlé par permissions, et routé uniquement vers les gestionnaires enregistrés. Le pont donne à votre UI web l'accès aux API natives de la plateforme.


🚀 Installation Pas à Pas

Étape 1 : Installer le CLI

npm install -g zero-native

Vérifiez l'installation :

zero-native --help

Étape 2 : Créer un Nouveau Projet

zero-native init mon-app-desktop --frontend svelte
cd mon-app-desktop

Cela génère :

  • app.zon — Le manifeste de l'application
  • build.zig — La configuration de build Zig
  • src/ — Les fichiers sources Zig
  • frontend/ — Votre projet Svelte

Étape 3 : Configurer app.zon

Ouvrez app.zon et personnalisez-le :

.{
    .id = "com.example.mon-app-desktop",
    .name = "mon-app-desktop",
    .display_name = "Mon App Desktop",
    .version = "0.1.0",
    .web_engine = "system",
    .permissions = .{ "window" },
    .capabilities = .{ "webview", "js_bridge" },
    .security = .{
        .navigation = .{
            .allowed_origins = .{ "zero://app", "http://127.0.0.1:5173" },
        },
    },
    .windows = .{
        .{ .label = "main", .title = "Mon App", .width = 960, .height = 640 },
    },
}

Étape 4 : Ajouter un Appel au Pont Natif

Dans votre frontend Svelte (frontend/src/App.svelte) :

<script>
  import { onMount } from "svelte";

  let statutPont = $state("vérification...");
  let message = $state("");

  onMount(async () => {
    if (window.zero) {
      statutPont = "connecté";
      const resultat = await window.zero.invoke("hello", { name: "Monde" });
      message = resultat;
    } else {
      statutPont = "non disponible";
    }
  });
</script>

<main>
  <h1>Application Desktop Zero-Native</h1>
  <p>Pont : {statutPont}</p>
  <p>Réponse : {message}</p>
</main>

Étape 5 : Compiler et Exécuter

zig build run

La première compilation installe les dépendances frontend, compile le shell Zig, et ouvre une fenêtre desktop affichant votre UI Svelte.

Étape 6 : Build de Production

Pour un package distribuable :

zig build package -- -Dpackage-target=macos

Cela produit un bundle .app autonome (ou .exe pour Windows, AppImage pour Linux) dans zig-out/. La taille du binaire avec le WebView système est inférieure à 1 Mo.


✅ Checklist de Vérification

  • CLI installé : zero-native --help affiche les commandes
  • Projet créé : zero-native init mon_app --frontend svelte
  • app.zon contient des métadonnées valides
  • Le serveur de développement frontend démarre
  • Une fenêtre native s'ouvre avec votre UI
  • Le pont fonctionne : window.zero.invoke() retourne une réponse
  • Le package de production se compile sans erreur
  • La taille du binaire est inférieure à 2 Mo

📚 Ressources