C++26 terminal UI: a type-state compile-time DSL, Yoga flexbox,
a SIMD cell-diff renderer, and jaal as its runtime.
Quickstart · Examples · Widgets · Docs · Building
- Compile-time UI trees.
t<"Hello"> | Bold | border_<Round>is type-state safe — try to set border color without a border and it's a compile error, not a runtime no-op. - SIMD frame diff. AVX2 / SSE4.2 / NEON. 64-bit packed cells, O(1) compare. Only changed cells write to the terminal.
- Real flexbox. Yoga layout —
grow(),gap(),align(),justify(). Noprintfcolumn-counting. - row / col. The GTK box model with responsiveness built in:
row({cpu, mem, net, disk})puts cells side by side sharing the width — and wraps, then stacks, by itself as the terminal narrows.col()stacks cells that fill the width. Addsidebar(stats, table, 42)and a whole three-shape dashboard is two lines — no breakpoints, no spans, zero width arithmetic. See Responsive Layouts. - Responsive by measurement.
fit_rowsheds low-priority items when narrow,fit_colsheds low-priority panels when SHORT,pickshows the richest alternative that actually fits (SwiftUI's ViewThatFits),clampstops an ultrawide from stretching your UI thin (libadwaita's AdwClamp),solve_columnskeeps a table's header and rows on one width plan,fillsizes a graph to its slot,placepins content to any corner — all measured, never hand-estimated. - A real data table.
Tabledoes selection (▎ cursor + selected-row strip + ↑↓/j/k/PgUp/PgDn), height-aware windowing with a scrollbar (tbl.build() | grow(1)— the row count falls out of the layout), host-owned scroll (window_topfor sticky scroll-margins), sort indicators, flexible columns that truncate with …, column shedding when narrow, per-row/header click rects via the hit registry — and RICH cells: styled spans inside one cell (tree rails, dim argv trails) that clip with the truncation, andTableCell::dyncells painted at the column's solved width (inline meters). htop's working set, one widget — seeexamples/proc_table.cpp. - Pretty by default.
gradient("MAYA", a, b)sweeps color across text,rainbow()does the full spectrum,gradient_rule()draws a divider that re-tiles to its pane — oneTextElementunder the hood, so it wraps and measures like plain text. See Gradients. - Two render modes. Fullscreen (alternate screen) or inline (lives in your scrollback, doesn't take over the terminal).
- maya is to jaal what Ink is to React. jaal is the runtime: a program's model, one
updateper message, effects (Cmd) and subscriptions (Sub), timers, threads, streams, shutdown, all checked at compile time. maya draws it. One way to write an app, andview()is a pure function of the model. - Header-mostly.
<maya/host/run.hpp>for an app,<maya/maya.hpp>for the view layer alone (static output, tests). Widgets opt-in individually.
Static UI — fully resolved at compile time:
#include <maya/maya.hpp>
using namespace maya::dsl;
int main() {
constexpr auto ui = v(
t<"Hello World"> | Bold | Fg<100, 180, 255>,
h(
t<"Status:"> | Dim,
t<"Online"> | Bold | Fg<80, 220, 120>
) | border_<Round> | bcol<50, 55, 70> | pad<1>
);
maya::print(ui.build());
}An app — the model, one update per message, a pure view, and the events it listens to:
#include <maya/host/run.hpp>
using namespace maya;
using namespace maya::dsl;
struct Counter {
struct Model { int count = 0; };
struct Inc {}; struct Dec {}; struct Quit {};
using Msg = std::variant<Inc, Dec, Quit>;
using Cmd = jaal::Cmd<Msg>;
using Sub = jaal::Sub<Msg, on_key>; // say which event sources you use
static Cmd update(Model& m, Inc) { ++m.count; return {}; }
static Cmd update(Model& m, Dec) { --m.count; return {}; }
static Cmd update(Model&, Quit) { return Cmd::quit(0); }
static Element view(const Model& m) {
return v(
text("Count: " + std::to_string(m.count)) | Bold | Fg<100, 200, 255>,
t<"[+/-] change [q] quit"> | Dim
) | border_<Round> | bcol<50, 55, 70> | pad<1>;
}
static Sub subscribe(const Model&) {
return keys<Sub>({{'+', Inc{}}, {'-', Dec{}}, {'q', Quit{}}});
}
};
int main() { return run<Counter>({.title = "counter"}); }That's examples/counter.cpp. Animation is a
subscription (Sub::every(16ms, Tick{})) that you drop when nothing moves,
background work is Cmd::task, and pixel art is a view like any other:
pixels(image) draws an Image with half blocks. Pass
.mode = Mode::Inline to render in the scrollback instead of taking the
screen.
54 examples ship with the framework, every one a jaal program:
Plus FPS raycaster, raymarcher, fluid sim, mandelbrot zoom, matrix rain, particle systems, spectrum analyzer, breakout, snake, music player, system monitor, AI agent demos, and more. All under examples/.
moha — a native terminal client for Claude — is built on maya in production.
Data: Line chart · Bar chart · Gauge · Sparkline · Heatmap · Flame chart · Waterfall · Token stream · Context window · Git graph
Input: Text input · Textarea · Select · Slider · Checkbox · Radio · Button · Menu · Command palette
Layout: Table · Tabs · Tree · Scrollable · Modal · Popup · Toast · Disclosure · Divider · Breadcrumb
Display: Markdown · Inline diff · Diff view · Badge · Spinner · Progress bar · Calendar · Canvas · Image · Log viewer
Agent UI: Tool call · Bash tool · Read tool · Edit tool · Write tool · Fetch tool · Message · Thinking block · Streaming cursor · Activity bar · Permission prompt · Model badge · Cost tracker · Git status · File changes · System banner · Error block · Turn divider · Conversation view · Plan view · API usage
- The runtime is jaal:
Model+ oneupdate(Model&, Case)per message +view+subscribe. A missing handler, an unused effect, or a subscription the host can't provide is a compile error. - Effects as data:
Cmd::quit,after,task,send, batches, plus the terminal's own (set_title,write_clipboard,commit_scrollback,suspend,set_mouse, ...), listed in the program'sCmdtype. - Subscriptions as data:
Sub::every,Sub::stream, and maya's event sourceson_key,on_mouse,on_paste,on_focus,on_resize. Diffed every step, so a timer that stops being subscribed stops. - Frame flow control: never more than one frame ahead of the terminal, so
qis instant even over a slow link. - Signal / slot reactivity (SolidJS-inspired) for widgets that want it.
#include <maya/host/run.hpp> // run<P>(), event sources, terminal effects + all of maya.hpp
#include <maya/maya.hpp> // the view layer: DSL, elements, styles, print()
#include <maya/widget/input.hpp> // widgets included individually
#include <maya/internal.hpp> // canvas, diff engine, SIMD, terminal I/O (unstable)| Header | Contains | Stability |
|---|---|---|
app.hpp |
run, Program, keys, event sources, terminal effects (link maya::app) |
Stable |
maya.hpp |
DSL, elements, events, signals, styles, themes, print (link maya::maya) |
Stable |
widget/*.hpp |
90+ widgets | Stable |
internal.hpp |
Canvas, diff, renderer, SIMD, terminal I/O, layout | Internal |
Requires C++26. GCC 15+ recommended on all platforms.
# Arch
sudo pacman -S gcc cmake
cmake -B build && cmake --build build -j$(nproc)
# Ubuntu/Debian
sudo apt install g++-15 cmake
cmake -B build -DCMAKE_CXX_COMPILER=g++-15
cmake --build build -j$(nproc)
# Fedora
sudo dnf install gcc-c++ cmake
cmake -B build && cmake --build build -j$(nproc)AppleClang doesn't support C++26. Use Homebrew GCC:
brew install gcc@15 cmake
cmake -B build -DCMAKE_CXX_COMPILER=g++-15
cmake --build build -j$(sysctl -n hw.ncpu)MSYS2 (recommended):
pacman -S mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-cmake
cmake -B build -G "MinGW Makefiles"
cmake --build build -j%NUMBER_OF_PROCESSORS%Visual Studio 2025+:
cmake -B build -G "Visual Studio 17 2025"
cmake --build build --config ReleaseWSL2: follow the Linux instructions.
cmake -B build -DMAYA_BUILD_TESTS=ON
cmake --build build
ctest --test-dir buildTests link a non-LTO -O1 maya_test library by default (MAYA_FAST_TESTS=ON)
so a header edit recompiles + relinks in seconds instead of waiting on the
production -O3 + LTO build. Pass -DMAYA_FAST_TESTS=OFF to test against the
full optimized library.
maya is header-heavy, so the defaults are tuned for the edit-build-run loop:
| Option | Default | What it does |
|---|---|---|
MAYA_CCACHE |
ON | compile through ccache when installed |
MAYA_FAST_EXAMPLES |
ON | examples at -O1 (they were the biggest slice at -O3) |
MAYA_EXAMPLES_ALL |
OFF | keep the 55 demos out of the default target |
MAYA_FAST_TESTS |
ON | tests link a non-LTO -O0 library |
So ninja builds the library and tests; ninja maya_fps builds one demo and
ninja maya_examples builds them all. On an 8-core laptop: a cold build of
library + tests is ~95 s, a warm rebuild of everything ~1.5 s, and a real edit
to a core header ~12 s. Turn MAYA_FAST_EXAMPLES off to measure or ship a
demo at full optimisation.
cmake --install build --prefix /usr/localfind_package(maya 0.1 REQUIRED)
target_link_libraries(my_app PRIVATE maya::app) # or maya::maya for the view layer alone#include <maya/host/run.hpp>
#include <maya/widget/markdown.hpp>MIT.




