A retained-mode UI framework for C++ with a CSS-like styling system, a Flexbox-inspired layout engine, and a declarative DSL (ZMarkup + ZStyle) for building interfaces without writing boilerplate.
Status: 🗓️ beta — the core API is frozen. See docs/ROADMAP.md for what's still planned.
ZenitUI is a retained-mode UI framework written in modern C++. It
gives you the ergonomics of a web-like styling and layout system
(Flexbox, CSS transitions, @keyframes, ::part, var(), calc())
without pulling in a browser engine or a scripting runtime.
You write your UI in one of two ways, or both, mixed freely:
1. Imperative C++ — with a fluent API
auto root = std::make_shared<Layout>(LayoutType::Stack);
root->size(Percent(100), Percent(100));
root->getInlineBase().itemsH = Align::Center;
root->getInlineBase().itemsV = Align::Center;
auto btn = Btn("Click me", []{
std::printf("hello!\n");
});
btn->cls("btn-primary");
root->addChild(btn);2. Declarative — with ZMarkup + ZStyle
auto ui = ZMarkup::build(R"(
VStack#root {
Button.btn-primary#ok "Click me"
}
)");
ui.onClick("ok", []{ std::printf("hello!\n"); });/* assets/game.zstyle */
.btn-primary {
background: #0079F1;
radius: 8px;
padding: 1.5vh 2vw;
transition: background 0.25s ease-out, scale 0.12s ease-out-back;
}
.btn-primary:hover { background: #3296FF; scale: 1.15; }
.btn-primary:pressed { background: #505050; scale: 0.90; }Both approaches produce the same tree of Layout nodes, and both are
fully compatible with the same styling system.
| Area | What you get |
|---|---|
| Layout | Stack, Vertical, Horizontal flow, grow / shrink / gap, absolute positioning, overflow: scroll | auto, z-index, portals |
| Units | px, %, vw, vh, pw, ph, auto, and full calc() support |
| Styling | CSS-like cascade, descendant selectors, :hover :pressed :focus :checked :disabled, ::part, custom properties (--x / var()), media queries |
| Animations | Per-property transitions, CSS @keyframes, imperative UIAnimation, per-::part animations |
| Input | Hover / pressed / focus state machine, Tab navigation, pointer capture, passThrough, right-click, context menus |
| Effects | filter: blur(...) drop-shadow(...), arbitrary shaders, extensible FilterRegistry, box-shadow |
| Widgets | Text, Button, Panel, Toggle, Checkbox, Slider, ProgressBar, TextInput, ScrollView, Dropdown, Modal, Tooltip, Popup, Canvas, ImageContainer |
| Backends | Pluggable. Reference implementation for Raylib, plus a headless mock backend for tests and CI. The core only depends on IRenderer / IPlatform / IAssetProvider. |
| Debugging | Optional Debug::dumpStyle / dumpTree / dumpTheme / dumpParts, structured Logger with source locations |
| Data binding | Bindable<T> — observable values with RAII subscriptions, lifetime-safe, zero dependencies |
| Testing | The ZenitUI::MockBackend target provides MockRenderer, MockPlatform, and MockAssetProvider for full headless interaction tests. |
#include <raylib.h>
#include "RaylibBackend.hpp"
#include "ZenitUI.hpp"
using namespace ZenitUI;
int main() {
InitWindow(800, 600, "ZenitUI");
SetWindowState(FLAG_WINDOW_RESIZABLE);
RaylibRenderer renderer;
RaylibPlatform platform;
RaylibAssetProvider assets(renderer);
UIContext::get().renderer = &renderer;
UIContext::get().platform = &platform;
UIContext::get().assets = &assets;
auto root = std::make_shared<Layout>(LayoutType::Stack);
root->size(Percent(100), Percent(100));
root->getInlineBase().itemsH = Align::Center;
root->getInlineBase().itemsV = Align::Center;
root->addChild(Btn("Hello!", []{ std::printf("hi\n"); }));
while (!WindowShouldClose()) {
root->runFrame(GetFrameTime());
BeginDrawing();
ClearBackground(BLACK);
root->renderFrame();
EndDrawing();
}
CloseWindow();
}A full walkthrough is in Getting Started.
ZenitUI uses CMake as its primary build system. A thin Makefile
at the repository root preserves the classic developer workflow and
delegates every step to CMake — there is no drift between the two.
- CMake ≥ 3.20
- A C++20 compiler (GCC, Clang, MSVC, or MinGW-w64)
- Ninja (recommended; falls back to the default generator if absent)
- Git (used by
FetchContentto pull Raylib for the examples)
git clone https://github.com/zLouis043/ZenitUI
cd ZenitUI
make # build libzenit.a + unit tests
make test # run the test suite
make examples # build all examples (fetches Raylib automatically)
make run-00_demo # build + run a specific example
make bench # build the benchmark harness
make dist # install into dist/ for downstream consumptionThe examples and run-* targets fetch and build Raylib on demand.
There is no deps/raylib/ folder in the repo — the build is
self-contained.
The Makefile is purely a convenience. The same workflow works with plain CMake:
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build build
ctest --test-dir build --output-on-failuremake dist # → dist/ with headers, libzenit.a, and CMake configdist/ contains everything a consumer project needs, in a stable
layout:
dist/
├── include/... # public headers
├── lib/libzenit.a # static library
└── lib/cmake/ZenitUI/... # CMake package files
The target is always ZenitUI::ZenitUI, whether you consume the
library from source or from an install. Your CMakeLists.txt doesn't
change.
add_subdirectory(third_party/ZenitUI)
target_link_libraries(my_app PRIVATE ZenitUI::ZenitUI)find_package(ZenitUI REQUIRED)
target_link_libraries(my_app PRIVATE ZenitUI::ZenitUI)cmake -B build -DCMAKE_PREFIX_PATH=/path/to/ZenitUI/distThe public interface (include dirs, C++20, warning flags) is propagated automatically.
The core is backend-agnostic; backends are opt-in. Currently available:
| Backend | CMake target | Purpose |
|---|---|---|
raylib |
ZenitUI::RaylibBackend |
Reference implementation |
mock |
ZenitUI::MockBackend |
Headless testing (no GPU, no window) |
Enable them with -DZENITUI_BACKENDS="raylib;mock" (semicolon-separated).
Tests automatically pull in mock; examples default to raylib.
target_link_libraries(my_app PRIVATE
ZenitUI::ZenitUI
ZenitUI::RaylibBackend
)make libzenit # → build/libzenit.a
g++ main.cpp -I/path/to/ZenitUI/include -L/path/to/ZenitUI/build -lzenit -std=c++20ZenitUI/
├── include/ # public headers (installed)
│ └── components/ # widget headers
├── src/
│ ├── layout/ # layout engine, style resolver, measure/arrange
│ └── internal/ # animation, input, scroll, ZMarkup parser, …
├── backends/
│ ├── raylib/ # reference backend (fetches Raylib)
│ └── mock/ # headless mock backend for tests
├── examples/ # one folder per example (each has main.cpp)
├── tests/ # unit tests
├── benchmarks/ # benchmark harness
├── docs/ # user, internals, api
├── build/ # CMake build tree (gitignored)
└── dist/ # installed artifacts via `make dist` (gitignored)
The full documentation lives in docs/:
- Getting Started — setup, first layout, first widget
- Cheatsheet — one-page reference for day-to-day work
- Glossary — terminology used across the framework
- Layout — Stack / Vertical / Horizontal, flexbox, units
- Widgets — reference for every built-in widget
- ZMarkup — the declarative DSL
- ZStyle — the
.zstylestyling language - Events — click, hover, focus, bubbling, pointer capture
- Animations — transitions and keyframes
- Portals — Popup, Tooltip, Dropdown, Modal
- Effects — filters, shaders,
FilterRegistry - Custom Widgets — build your own component
- Data Binding —
Bindable<T>for model ↔ UI
- Architecture — high-level view, the frame pipeline
- Lifecycle —
update/measure/arrange/draw - Style System — cascade, specificity, inheritance
- Animation System — imperative vs CSS vs
::part - Layout Engine — intrinsic size, flow, dirty tracking
- Render Pipeline — inline vs layer, filters, portals
- Input System — FSM, focus, pointer capture
- Scroll System —
ScrollState, arrange, inertia - Backend — implementing
IRenderer/IPlatform - Debugging — dumps, logging, headless tests
See CONTRIBUTING.md.
ZenitUI is in beta. The core API is frozen: widgets, styling, layout, animations, events, and the backend interfaces described in the user guide and API reference are stable. Breaking changes are still possible for work that lands in Phases 4–8 of the roadmap (performance, feature completion, tooling), but the surface described in Phases 1–3 will not change.
See docs/ROADMAP.md for the detailed plan from alpha to beta to 1.0, including known issues and planned features.
MIT © 2026 zLouis043