Model or dataset
duanebester/gooey avatar
duanebester/gooey

gooey is a GPU UI framework for Zig that admits its API is moving

Gooey is a hybrid immediate/retained mode UI framework designed for building fast, GPU-rendered applications on macOS/Metal, WebAssembly/WebGPU, and Wayland/Vulkan

648 stars6 forksZigMIT

At a glance

What is it?
gooey is an MIT licensed Zig framework for building GPU rendered applications on macOS, Wayland and the browser, with a declarative flexbox layout and zero external Zig dependencies, and its own readme warns that the API is still evolving.
Who is it for?
gooey fits a Zig developer who wants a native or browser UI with real GPU rendering and needs the whole stack under one licence and one build, particularly on macOS or on Wayland where nothing else in the ecosystem offers this combination. It does not fit a team that needs a stable API, since the readme states the API is evolving, and it does not fit a Linux user on X11, because the documented requirements name a Wayland compositor.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 3 days ago.
What is it written in?
Mainly Zig, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A Zig UI stack with a warning label on it

gooey describes itself as a GPU-accelerated UI framework for Zig, targeting macOS with Metal, Linux with Vulkan and Wayland, and the browser with WASM and WebGPU. The feature list runs to nineteen entries, from GPU rendering with MSAA anti-aliasing through drag and drop, theming, IME support and built-in screen reader support with semantic roles and live regions.

The readme also puts a bold note at the top: early development, API is evolving. That is the first thing a reader should register, and it changes how the rest of the documentation should be read. There is a CI badge and a Discord link, and the example application named as a demonstration is chat-zig, an Anthropic Claude client built on the Zig 0.16 std.Io stack for asynchronous HTTP.

The framing of the project is worth stating plainly. This is not a widget library for a general application, and it is not a port of an existing tool. It is a rendering stack, a layout system and an application shell assembled from scratch in Zig, which is why the dependency story is unusual: the readme says there are zero external Zig package dependencies, that build.zig.zon lists no dependencies, and that the Objective-C runtime bindings are vendored in the tree. It links only against platform system frameworks and libraries.

Cx for state, ui.* for layout, and state that can be tested alone

The architectural decision that shapes everything else is the split between Cx and the ui primitives. Cx holds state, handlers and focus, while the ui.* functions are the layout primitives, and the readme gives that separation its own bullet point in the feature list.

The handlers are described as a pure state pattern, with testable state methods and automatic re-rendering. The todo example makes the intent concrete: a pure, UI-free state model, handlers through cx.update, cx.updateWith and cx.command, a bound TextInput, a Checkbox and a Button, list iteration, and unit tests that exercise the state with no UI in play.

That is the part of the design most worth stealing, even if you never use the renderer. If the state model of an interface can be tested without a window, then the tests run in milliseconds on any machine, they do not need a GPU, and a headless CI runner can still cover the logic. The readme says the todo example's state model is covered by tests that run as part of zig build test.

The rest of the feature list follows the same split. Layout is component-based with a flexbox-style system, animations come with easing and tweenOn triggers, and an entity system handles dynamic creation and deletion with automatic cleanup, so the framework is aiming at applications whose contents change, not just forms.

Adding the dependency and the build wiring

Gooey is consumed the way a Zig package is consumed, which means a version pin with a hash in build.zig.zon:

zig
.dependencies = .{
    .gooey = .{
        .url = "https://github.com/duanebester/gooey/archive/refs/tags/v0.2.6.tar.gz",
        .hash = "gooey-0.2.6-uPBZ7jMhXQAhifhzm8HyurjFUx5S0BrLXq1ZDO0DcwTr",
    },
},

Then the module is imported in build.zig, and the readme notes the point of the arrangement: Gooey's module carries its platform linker requirements transitively, keeping consumer builds independent of Gooey's internal framework and library list. You do not have to know which system frameworks it needs per platform.

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

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});
    const gooey_dependency = b.dependency("gooey", .{
        .target = target,
        .optimize = optimize,
    });

    const executable = b.addExecutable(.{
        .name = "my-app",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
            .imports = &.{
                .{ .name = "gooey", .module = gooey_dependency.module("gooey") },
            },
        }),
    });
    b.installArtifact(executable);
}

Application source then uses const gooey = @import("gooey"), and the smallest complete program is short: a render function that calls cx.render with a box containing a text primitive, and an App type instantiated with the state type, a pointer to the state and the render function.

Optional sibling modules for controls, charts and generated UI

The same dependency also exports optional sibling modules, and the readme is careful to say that you add only the ones your application uses. The import list in build.zig grows to four entries:

zig
.imports = &.{
    .{ .name = "gooey", .module = gooey_dependency.module("gooey") },
    .{ .name = "gooey-components", .module = gooey_dependency.module("gooey-components") },
    .{ .name = "gooey-charts", .module = gooey_dependency.module("gooey-charts") },
    .{ .name = "gooey-genui", .module = gooey_dependency.module("gooey-genui") },
},

Each has a defined role. gooey-components provides high-level controls built from Gooey's public primitives and state engines, which is the layer where you would find the standard widget set. gooey-charts provides optional GPU-accelerated charts.

The third one is the interesting one. gooey-genui provides catalog-constrained generated UI with direct TypeSafe Jev evaluation and, notably, no Vercel AI Gateway dependency. That is a specific architectural position: a generated interface described against a catalog of allowed elements, evaluated locally through a typed expression language, without calling out to a hosted model gateway. For a desktop or browser application that does not want a network round trip to a model provider in its rendering path, that constraint is the feature.

The repository layout matches, with components/, charts/ and genui/ directories at the top level next to src/, each with its own README.

One rendering model, three very different backends

The rendering story is where the platforms diverge, and the readme lists the requirements per platform. macOS needs 12.0 or later. Linux needs a Wayland compositor, Vulkan drivers, FreeType, HarfBuzz, Fontconfig, libpng and D-Bus. The stated Zig requirement is 0.16.0 or later.

Text rendering is handled natively per platform: CoreText on macOS, FreeType with HarfBuzz on Linux, and Canvas in the browser. Custom shaders can be dropped in as Metal or GLSL sources, and the example list includes a space dashboard with a shader as a separate program, so the shading path is exercised rather than only promised.

The macOS-only feature is Liquid Glass, described as the transparent window effects in macOS 26.0 Tahoe, and it is listed as its own example program, which suggests it is a platform-specific effect.

Two platform details are worth noticing because they are absences. There is no X11 requirement for Linux, only Wayland, so an X11-only desktop is out of scope. And there is no Windows target listed at all, which for a UI framework is the difference between a platform tool and a cross-platform one. In the browser, the target is WebGPU, which narrows the audience to browsers that implement it.

The example programs are the real documentation

The readme's longest code block is a list of run targets, and it doubles as a catalogue of what the framework can do:

bash
zig build run              # Showcase demo
zig build run-counter      # Counter example
zig build run-todo         # Todo app (state, handlers, TextInput, lists)
zig build run-animation    # Animation demo
zig build run-pomodoro     # Pomodoro timer
zig build run-genui-demo      # Generated UI composition and native controls
zig build run-virtual-list # Variable-height list
zig build run-data-table   # Virtualized table (10k rows)
zig build run-code-editor  # Code editor with syntax highlighting
zig build test             # Run tests

Reading the list tells you more about the framework's maturity than any feature bullet does. There is a virtualized list and a virtualized table sized at ten thousand rows and items, a code editor with syntax highlighting, a dropdown select, a tooltip, a modal, image loading and styling, a native file dialog, an actions and keybindings demo, a layout demo covering flexbox and shrink and text wrapping, an entity system demo, and a generated UI demo.

The list also implies the size of the surface. Each of those is a separate program, and a framework that ships twenty example applications is a framework with a large API, which is consistent with the readme's own warning that the API is evolving.

Elsewhere in the tree there is a web/ directory for the browser target, a valgrind.supp file, a CLAUDE.md and an .agents/ directory, and a docs/ directory.

MIT terms, and a version story that does not line up

The project is released under the MIT Licence, and the repository is not archived with the last push on 2026-10-20, so the work is current.

The release history is where the friction is. The three most recent releases are v0.3.0, published on 2026-10-07 and titled for Zig 0.17.0, v0.2.9 on 2026-09-01, and v0.2.8 on 2026-08-28, titled for pointer interactions and virtual-list control. The cadence is roughly monthly, which is fast for a project whose readme says the API is evolving.

That leaves a mismatch you will hit immediately if you follow the quick start literally. The requirements line says Zig 0.16.0 or later, the latest release is built for Zig 0.17.0, and the dependency example still points at the v0.2.6 tarball with its hash. Someone copying the readme today would install a release several versions behind the newest one, on a Zig version claim the newest release no longer matches.

The honest reading is that the documentation lags the releases, which is common in a fast moving young project and is exactly why the early development label matters. Pin a release deliberately, check which Zig version that release expects, and expect the readme's install snippet to be out of date.

Editorial conclusion

gooey fits a Zig developer who wants a native or browser UI with real GPU rendering and needs the whole stack under one licence and one build, particularly on macOS or on Wayland where nothing else in the ecosystem offers this combination. It does not fit a team that needs a stable API, since the readme states the API is evolving, and it does not fit a Linux user on X11, because the documented requirements name a Wayland compositor. Before adopting it, pin a release and check that the Zig version matches, since v0.3.0 targets Zig 0.17.0 while the quick start still says Zig 0.16.0 and still shows the dependency pinned to v0.2.6, and confirm the runtime you need is Metal, Wayland with Vulkan, or a WebGPU browser.

Frequently asked questions

What are the requirements for building an app with gooey?

The readme lists Zig 0.16.0 or later, macOS 12.0 or later, and on Linux a Wayland compositor, Vulkan drivers, FreeType, HarfBuzz, Fontconfig, libpng and D-Bus. There are no external Zig package dependencies, since build.zig.zon lists none.

How do I add gooey to a Zig project?

Add the release tarball with its hash to build.zig.zon under .dependencies, then import the module in build.zig with a .imports entry mapping the name gooey to gooey_dependency.module("gooey"). The module carries Gooey's platform linker requirements transitively.

Can gooey run in the browser?

Yes. The browser target is WASM with WebGPU, and text is rendered with Canvas there, and the repository has a web/ directory for it. The readme does not list a Windows target.

What is the difference between Cx and the ui.* primitives?

Cx holds state, handlers and focus, while the ui.* functions are the layout primitives. That separation is what lets the state model be unit tested with no UI in play, and it is why the todo example's state is covered by tests that run under zig build test.

What does the gooey-genui module do?

It provides catalog-constrained generated UI with direct TypeSafe Jev evaluation and no Vercel AI Gateway dependency, and it is exported as an optional sibling module alongside gooey-components and gooey-charts.

Official sources

  1. duanebester/gooey on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/duanebester-gooey.svg)](https://hysenlabs.com/projects/duanebester-gooey)

Was this useful?