A cross-platform, declarative UI library written in pure Zig that brings a native macOS / SwiftUI look and feel to macOS, Linux, and Windows.
| Project name | zigui |
| Language | Zig (target: 0.16.x stable; track latest stable) |
| License | MIT |
| Status | Pre-alpha, usable toolkit — core + post-v0 features implemented & tested (146 tests, green on macOS + Linux) |
| Last updated | 2026-06-04 |
The full pure-Zig core is built and test-driven (146 tests, green on macOS and inside Linux Docker):
- Foundation: geometry, color (+ alpha compositing), observable
State(T)/Binding(T), two-pass layout engine (stacks / spacer / padding / frame / alignment), tokenized macOS light & dark themes. - Rendering: retained
Canvascommand list + a pure-Zig software rasterizer with SDF anti-aliasing (rounded rects, strokes, gradients, lines, clipping, glyph & image blits). This is what makes the whole visual layer unit-testable headlessly. - Text: a from-scratch TrueType parser + glyph rasterizer (cmap 4/12, simple
& composite
glyf, quadratic beziers), glyph cache, shaping/measurement and word-wrap, bundled Inter (OFL). - View layer: declarative value-tree with fluent modifiers, hit testing,
ForEach, focus management. - Components (~22):
TextLabelImageTextField·ButtonToggleSliderStepperProgressViewPicker·VStackHStackZStackSpacerDividerScrollViewListForEach·RectangleRoundedRectangleCircleCapsuleEllipseLinearGradient. - Backend: an SDL3 window + event loop (
src/app.zig) presenting the software-rasterized framebuffer, with mouse, text-input and key routing. - Examples:
hello(counter) andsettings(the macOS-like milestone demo); CI matrix (macOS/Linux/Windows) + a LinuxDockerfile.
Renderer decision (supersedes §2's wgpu plan): the renderer rasterizes on
the CPU and presents through SDL3. This is a real, working,
identical-everywhere renderer that is fully testable headlessly and performs well
(build examples with -Doptimize=ReleaseFast). wgpu-native is dropped for now
— the SDL3 software-raster path is the chosen, shipping renderer, not a
placeholder. Because the Canvas command list cleanly isolates drawing, a GPU
backend could still be slotted in later behind the same interface with no
component changes, but it is no longer on the critical path.
The post-v0 feature set is now implemented: navigation (NavigationSplitView,
stack), TabView, modals (sheet/alert/popover, Menu/ContextMenu), grids
(LazyVGrid/LazyHGrid), animation, materials/blur, an accessibility node tree,
HiDPI scaling, wrapped text, app-owned scrolling, and a cross-platform system
tray (app.Tray). A streaming LLM-chat example (examples/llm-chat) exercises
them end-to-end.
SwiftUI is the gold standard for ergonomic, declarative, good-looking native
UI — but it is Apple-only and closed. zigui aims to deliver a SwiftUI-like
developer experience and macOS-like visual design on every desktop OS, using
its own rendering pipeline rather than wrapping native platform widgets.
On macOS, zigui draws with the same engine it uses on Linux and Windows — it
is not a translation layer to AppKit/SwiftUI. This guarantees pixel-identical
results across platforms and keeps the codebase a single, readable Zig project.
Non-goals: 100% SwiftUI API compatibility, source/binary compatibility with Swift, mobile (iOS/Android) support in v1, web/WASM target in v1.
- Readability over cleverness. This is an open-source framework people will read to learn from. Prefer explicit, well-named code over dense comptime metaprogramming. Comptime is a tool, not a goal.
- One renderer, all platforms. Identical output everywhere.
- SwiftUI as a north star, not a spec. We borrow concepts and naming where they help; we diverge freely where Zig idioms or clarity demand it.
- No hidden magic. State, layout, and rendering should be inspectable and debuggable.
- Minimal, vetted dependencies. Each C dependency must earn its place.
These were decided up front and frame the rest of the plan.
Update: the Rendering row below was the original plan. As shipped, the renderer is a pure-Zig CPU software rasterizer presented via SDL3, and wgpu-native has been dropped (see §0). The row is kept for history.
| Area | Decision | Rationale |
|---|---|---|
| Rendering | One code path, identical everywhere, fully testable headlessly, no GPU dependency to build/ship. wgpu dropped for now. | |
| API style | Comptime declarative tree (VStack(.{ Text("Hi"), Button(...) })) |
Closest feel to SwiftUI's declarative syntax while staying pure Zig. |
| Windowing/input | SDL3 | Battle-tested window + input + GPU-surface creation across all 3 OSes. |
| State / reactivity | Observable state + dirty flag | Explicit State(T); mutation marks subscribing views dirty and re-renders just those. Simpler & more debuggable than full tree diffing. |
| Fonts / text | Bundle Inter (OFL) + pure-Zig TrueType rasterizer + SDF atlas | SF-Pro-like look, legally clean, no C font dependency. |
| Look & feel | Themeable, macOS default theme | Ships looking like macOS everywhere; theming system allows custom/platform skins later. |
| v1 scope | Usable toolkit | Foundations + ~15–20 components + theming + state. Target: build a real settings-screen demo app. |
| Dependency | Type | Purpose | Justification |
|---|---|---|---|
SDL3 |
C lib | Windowing + input + present (examples only) | Avoids 3 hand-written platform layers. Linked only into example executables, never the core/tests. |
| Inter font | asset (OFL) | Default UI font | SF-Pro-like, redistributable. |
| Pure-Zig TTF rasterizer | Zig code | Glyph rasterization | Keeps text stack dependency-free. |
wgpu-nativewas in the original budget but has been dropped — the 2D renderer is a pure-Zig CPU rasterizer (no GPU dependency). Everything except SDL3 (layout, widgets, theming, state, text, the software rasterizer) is pure Zig with no external dependencies, and SDL3 links only into the example binaries — the library and its 146-test suite have zero C deps.
┌─────────────────────────────────────────────────────────────┐
│ Application code │
│ VStack(.{ Text("Hello"), Button("Tap", .{...}) }) │
├─────────────────────────────────────────────────────────────┤
│ View layer declarative views, modifiers, comptime │
│ tree construction │
├─────────────────────────────────────────────────────────────┤
│ State / reactivity State(T), bindings, dirty propagation │
├─────────────────────────────────────────────────────────────┤
│ Layout engine flexbox-like constraint solver, stacks, │
│ frames, padding, alignment │
├─────────────────────────────────────────────────────────────┤
│ Theming macOS default theme, design tokens, │
│ colors, typography, metrics, dark mode │
├─────────────────────────────────────────────────────────────┤
│ 2D vector renderer rounded rects, strokes, gradients, │
│ (pure Zig) shadows, clipping, text quads │
├──────────────────────────────┬──────────────────────────────┤
│ Text engine │ Render backend │
│ Inter + pure-Zig TTF │ pure-Zig CPU rasterizer │
│ + glyph cache │ (SDF AA) → framebuffer │
├──────────────────────────────┴──────────────────────────────┤
│ Platform layer SDL3: window, event loop, input, present │
└─────────────────────────────────────────────────────────────┘
zigui/
├── build.zig # SDL3 linked into examples only
├── build.zig.zon # no external zig deps (SDL3 linked from the system)
├── PRD.md
├── README.md
├── LICENSE # MIT
├── assets/
│ └── fonts/Inter-*.ttf # OFL
├── src/
│ ├── zigui.zig # public root / re-exports
│ ├── app.zig # App, Scene, WindowGroup, run loop
│ ├── platform/
│ │ └── sdl.zig # window, events, surface
│ ├── render/
│ │ ├── canvas.zig # 2D draw-command list (backend-neutral seam)
│ │ ├── raster.zig # pure-Zig CPU rasterizer (SDF AA) → framebuffer
│ │ └── color.zig # (the proposed gpu/ wgpu backend was dropped)
│ ├── text/
│ │ ├── ttf.zig # pure-Zig TrueType parser/rasterizer
│ │ ├── atlas.zig # SDF glyph atlas
│ │ ├── shape.zig # layout, line breaking
│ │ └── font.zig
│ ├── layout/
│ │ ├── engine.zig # measure/arrange passes
│ │ ├── stack.zig # V/H/Z stack solvers
│ │ └── geometry.zig # Size, Rect, EdgeInsets, Alignment
│ ├── state/
│ │ ├── state.zig # State(T), Binding(T)
│ │ └── observe.zig # dirty propagation
│ ├── view/
│ │ ├── view.zig # View interface, modifier chain
│ │ ├── modifier.zig # padding, frame, background, etc.
│ │ └── tree.zig # retained node tree + reconciliation
│ ├── theme/
│ │ ├── theme.zig # Theme struct, tokens
│ │ └── macos.zig # default macOS theme values
│ └── components/
│ ├── text.zig
│ ├── button.zig
│ ├── stacks.zig
│ ├── ... # see component inventory
│ └── ...
├── examples/
│ ├── hello/
│ └── settings/ # v1 milestone demo
└── tests/
This is the working catalog of SwiftUI's surface area, mapped to zigui
equivalents with target tiers. Naming follows SwiftUI where it reads well in
Zig; modifiers become chained methods or fields in the comptime config struct.
Tier legend: T1 = v1 "usable toolkit" must-have · T2 = v1.x fast-follow · T3 = later / nice-to-have · N/A = out of scope or platform-specific.
| SwiftUI | zigui | Tier | Notes |
|---|---|---|---|
VStack / HStack / ZStack |
VStack / HStack / ZStack |
T1 | Core layout primitives. |
Spacer |
Spacer |
T1 | Flexible space. |
Divider |
Divider |
T1 | |
Grid / GridRow |
Grid |
T2 | |
LazyVStack / LazyHStack |
LazyVStack / LazyHStack |
T2 | Virtualized. |
LazyVGrid / LazyHGrid |
LazyVGrid / LazyHGrid |
T2 | |
ScrollView |
ScrollView |
T1 | Needs clipping + scroll input. |
List |
List |
T1 | Core; sectioned in T2. |
Form |
Form |
T2 | Settings-style grouped layout. |
Section |
Section |
T2 | |
GeometryReader |
GeometryReader |
T2 | |
ViewThatFits |
ViewThatFits |
T3 |
| SwiftUI | zigui | Tier | Notes |
|---|---|---|---|
Text |
Text |
T1 | Styling, weight, color, multiline. |
Label |
Label |
T2 | Text + icon. |
TextField |
TextField |
T1 | Editing, caret, selection. |
SecureField |
SecureField |
T2 | |
TextEditor |
TextEditor |
T2 | Multiline editing. |
Image |
Image |
T1 | PNG/decoded bitmap. |
AsyncImage |
AsyncImage |
T3 | Needs async/IO story. |
| SF Symbols | Symbol (bundled icon set) |
T2 | Use an open icon set (e.g. Lucide/Phosphor) — SF Symbols are proprietary. |
| SwiftUI | zigui | Tier | Notes |
|---|---|---|---|
Button |
Button |
T1 | Variants/styles. |
Toggle |
Toggle |
T1 | macOS switch look. |
Slider |
Slider |
T1 | |
Stepper |
Stepper |
T2 | |
Picker |
Picker |
T2 | Menu/segmented variants. |
Menu |
Menu |
T2 | Popover menu. |
DatePicker |
DatePicker |
T3 | |
ColorPicker |
ColorPicker |
T3 | |
ProgressView |
ProgressView |
T2 | Bar + spinner. |
Gauge |
Gauge |
T3 | |
Link |
Link |
T2 | |
SegmentedControl (Picker style) |
via Picker |
T2 |
| SwiftUI | zigui | Tier | Notes |
|---|---|---|---|
NavigationStack / NavigationSplitView |
NavigationStack / NavigationSplitView |
T2 | Sidebar + detail (mac-like). |
TabView |
TabView |
T2 | |
Sheet / .sheet |
Sheet |
T2 | Modal presentation. |
Popover / .popover |
Popover |
T2 | |
Alert / .alert |
Alert |
T2 | |
ConfirmationDialog |
ConfirmationDialog |
T3 | |
Toolbar |
Toolbar |
T2 | Window toolbar (mac titlebar style). |
ContextMenu |
ContextMenu |
T2 | |
| Menu bar / commands | Commands |
T3 | Native menu bar integration is platform-specific. |
| SwiftUI modifier | zigui | Tier |
|---|---|---|
.padding() |
.padding |
T1 |
.frame(width:height:) |
.frame |
T1 |
.background() |
.background |
T1 |
.foregroundStyle() / .foregroundColor() |
.foreground |
T1 |
.cornerRadius() / .clipShape() |
.cornerRadius / .clip |
T1 |
.font() |
.font |
T1 |
.opacity() |
.opacity |
T1 |
.shadow() |
.shadow |
T1 |
.overlay() / .border() |
.overlay / .border |
T1 |
.offset() / .position() |
.offset / .position |
T2 |
.rotationEffect() / .scaleEffect() |
.rotation / .scale |
T2 |
.onTapGesture / .gesture |
.onTap / .gesture |
T1 / T2 |
.animation() / withAnimation |
.animation |
T2 |
.disabled() |
.disabled |
T1 |
.hidden() |
.hidden |
T2 |
.zIndex() |
.zIndex |
T2 |
| SwiftUI | zigui | Tier | Notes |
|---|---|---|---|
@State |
State(T) |
T1 | Observable value + dirty flag. |
@Binding |
Binding(T) |
T1 | Two-way reference into a State. |
@Observable / ObservableObject |
Observable |
T2 | Object-level observation. |
@Environment / EnvironmentValues |
Environment |
T2 | Theme, color scheme injection. |
@EnvironmentObject |
(covered by Environment) |
T2 | |
@StateObject |
StateObject |
T2 | Owned reference state. |
ForEach |
ForEach |
T1 | Collection → views, keyed. |
@AppStorage |
AppStorage |
T3 | Persisted settings. |
| SwiftUI | zigui | Tier |
|---|---|---|
Rectangle / RoundedRectangle |
Rectangle / RoundedRectangle |
T1 |
Circle / Ellipse / Capsule |
Circle / Ellipse / Capsule |
T1 |
Path |
Path |
T2 |
LinearGradient / RadialGradient / AngularGradient |
LinearGradient / RadialGradient / AngularGradient |
T1 / T2 |
Canvas |
Canvas |
T2 |
.blur() / material / .background(.ultraThinMaterial) |
Material / .blur |
T3 |
| SwiftUI | zigui | Tier | Notes |
|---|---|---|---|
App protocol |
App |
T1 | Entry point. |
WindowGroup / Scene |
WindowGroup / Scene |
T1 | |
Settings scene |
Settings |
T3 | |
@main |
zigui.run(App) |
T1 | Zig has no @main attribute; explicit run. |
const zigui = @import("zigui");
const Text = zigui.Text;
const Button = zigui.Button;
const VStack = zigui.VStack;
fn ContentView(state: *AppState) zigui.View {
return VStack(.{ .spacing = 12 }, .{
Text("Welcome to zigui")
.font(.title)
.foreground(.primary),
Text(state.message)
.foreground(.secondary),
Button("Click me", .{ .onTap = onClick })
.padding(8),
});
}Open design questions to resolve in implementation (documented, not yet decided):
- How children of heterogeneous types are stored in the comptime tuple and type-erased into the retained node tree.
- Modifier chaining ergonomics (
.padding(8).background(...)) vs a config struct, and how to keep error messages readable. - Memory ownership: arena-per-frame for transient view structs vs retained nodes that persist across frames.
var count = zigui.State(i32).init(0);
// reading subscribes the current view; writing marks subscribers dirty
Button("Increment", .{ .onTap = struct {
fn f() void { count.set(count.get() + 1); }
}.f });
Text(zigui.fmt("Count: {d}", .{count.get()}));State(T)holds a value plus a list of subscribing view nodes.- Reading inside a view's body records a subscription.
set()(orupdate()) marks subscribers dirty; the run loop re-renders only dirty subtrees on the next frame.Binding(T)is a typed two-way handle into aState(T)for controls likeTextFieldandToggle.
A two-pass, SwiftUI-inspired layout protocol:
- Measure / propose: parent proposes a size; child returns its desired size given that proposal.
- Arrange / place: parent assigns final frames to children based on
alignment, spacing, and
Spacer/flexible-frame rules.
Stacks, frames, padding, and alignment guides are the core. The engine is pure Zig and unit-testable without a GPU.
- Layout produces a tree of placed nodes with absolute rects.
- Each node emits draw commands to the 2D
Canvas(rounded rects, strokes, gradients, blur/material, clip pushes/pops, glyph & image quads). - The pure-Zig software rasterizer executes the command list into an RGBA framebuffer with SDF anti-aliasing (and, on HiDPI, at device resolution).
- SDL3 uploads the framebuffer to a streaming texture and presents it.
Text: glyphs are rasterized by the pure-Zig TTF engine and kept in a glyph cache keyed by glyph + pixel size, so they are reused cheaply across frames.
Themeis a struct of design tokens: color roles (primary,secondary,accent,background,controlBackground,separator, …), typography scale (largeTitle,title,headline,body,caption), spacing/metrics, corner radii, and control sizing.theme/macos.zigprovides the default token values tuned to match macOS (light + dark). This ships as the default everywhere.- Apps can supply a custom
Themeor override individual tokens viaEnvironment. A future platform-native theme can be added without touching component code. - Dark mode is a first-class theme variant from day one.
Legal note: we ship Inter (OFL) and an open icon set for symbols. We do not redistribute SF Pro, SF Symbols, or other Apple assets. The "mac look" is achieved through tuned tokens and metrics, not Apple assets.
Goal: a styled Text + Button in a resizable window on macOS, Linux,
and Windows, sharing one renderer.
-
build.zig+build.zig.zonwiring SDL3 on all 3 OSes (wgpu dropped). - Platform layer: window creation, event loop, input (SDL3).
- Renderer: pure-Zig CPU software rasterizer → framebuffer, presented via SDL3.
- 2D canvas: filled & stroked rounded rects, solid colors, clipping.
- Text engine: pure-Zig TTF parse + raster, SDF atlas, draw a text run.
- Layout engine: measure/arrange,
VStack/HStack, padding, frame. - State:
State(T), dirty flag, dirty-driven re-render. - View tree: comptime construction → retained node tree.
- Components:
Text,Button,VStack,HStack,Rectangle,Spacer. - Theme: macOS default tokens (light), color + typography.
-
examples/hello.
- Modifiers:
.background,.foreground,.cornerRadius,.shadow,.font,.opacity,.border,.overlay,.disabled,.onTap. -
ZStack,Divider, gradients (LinearGradient). -
TextFieldwith caret/selection;Binding(T). -
Toggle,Slider. -
ScrollViewwith clipping + scroll/wheel input. - Dark mode theme variant +
Environmentcolor scheme. - Shapes:
Circle,Capsule,RoundedRectangle.
-
List,ForEach(keyed),Section,Form. -
LazyVStack/LazyHStackvirtualization. -
Image(PNG decode),Label,Symbol(open icon set). -
ProgressView,Picker,Stepper,Menu. -
Observable/StateObject.
Goal: build a real settings-screen demo app end to end.
-
examples/settings: sidebar + form, toggles, sliders, pickers, text fields, sections — looking convincingly macOS-like on all 3 OSes. -
NavigationSplitView/ sidebar,TabView,Toolbar. -
Sheet,Popover,Alert,ContextMenu. - Basic animation (
.animation, transitions for show/hide). - Theming docs + custom theme example.
- Solid docs, API reference, getting-started guide.
- CI building + testing on macOS, Linux, Windows.
- ~15–20 polished components (see Tier T1 + key T2).
Navigation polish, TextEditor, Grid/LazyGrid, DatePicker,
ColorPicker, Canvas/Path drawing, materials/vibrancy/blur,
AsyncImage, accessibility (see §9), AppStorage, menu-bar commands,
gesture system, and broader SwiftUI parity.
- Unit tests for layout (golden size/frame assertions), state propagation, TTF parsing, and the path/geometry math — all runnable without a GPU.
- Snapshot/visual tests: render canvases to an offscreen buffer and compare against golden PNGs to catch visual regressions cross-platform.
- CI matrix: macOS + Linux + Windows; build all examples, run tests, produce snapshot diffs as artifacts.
zig fmtenforced; documented public API with doc comments.- Examples are tests: every example must build in CI.
| Topic | Question / risk | Plan |
|---|---|---|
| Comptime ergonomics | Heterogeneous children in a comptime tuple + type erasure into retained nodes; compile-time cost & error legibility. | Resolved: View is a single value type (a Kind union + Modifiers), so []const View children just work. Constructors accept a tuple literal (VStack(.{a, b})) and copy it into the build arena. Modifiers are plain methods returning a View by value — no comptime metaprogramming, readable errors. |
| Memory model | Per-frame arena for transient view structs vs persistent retained nodes. | Resolved: a per-frame arena holds the view tree, lowered layout nodes, draw commands and hit regions; it is reset (not freed) each frame and persists until the next rebuild, so hit regions stay valid during event dispatch. Observable state lives outside the arena (app-owned). |
| Text shaping | Complex scripts / RTL / ligatures / bidi not covered by a simple rasterizer. | v1 targets Latin/basic shaping; flag advanced shaping (HarfBuzz-equivalent) as post-v1. |
| Accessibility | No native a11y tree when we draw everything ourselves. | Out of scope for v1; design an accessibility node tree as a tracked post-v1 workstream (screen readers, focus). |
| HiDPI / scaling | Per-monitor DPI, fractional scaling across OSes. | Handle scale factor in the layout→render transform from v0.1; test on HiDPI displays. |
| API churn, build/distribution per OS. | Resolved by dropping it — the CPU software rasterizer needs no GPU dependency and is identical everywhere. A GPU backend behind the Canvas seam remains possible but is not planned. |
|
| Software-raster performance | CPU rasterizing a full HiDPI window each frame can be slow. | Event-driven loop (render on change, not a fixed FPS) + ship/run with ReleaseFast, where it is smooth. A GPU backend is the lever if it's ever needed. |
| IME / international input | Text input for non-Latin keyboards. | Rely on SDL3 text input events; full IME composition is post-v1. |
| "Mac look" fidelity | Matching macOS metrics/animation curves without Apple assets. | Tune theme tokens against reference screenshots; accept "convincingly mac-like," not pixel-identical to AppKit. |
zigui is a pure-Zig, single-renderer, declarative UI library that targets a
macOS/SwiftUI look and feel across macOS, Linux, and Windows. It renders with a
pure-Zig CPU software rasterizer presented via SDL3 (wgpu-native was dropped —
the SDL3 path works well and keeps the core C-dependency-free), a comptime
declarative view API, observable-state reactivity, and a bundled Inter font with
a pure-Zig text stack. It ships a "usable toolkit" of ~20 polished components plus
navigation, tabs, modals, grids, animation, materials, accessibility, HiDPI, and
a cross-platform system tray — proven by settings, showcase, and streaming
LLM-chat demos, with readability and open-source approachability as first-class
goals.