Immediate-mode UI components on Clay layout. 8 widgets, 4 renderer backends, slippy map with overlays. Same C code renders to WebGL, terminal, or CPU framebuffer.
Most UI libraries are married to a single rendering backend. ClayShards separates interaction from rendering: components are pure C functions that produce Clay layout commands. Plug in any renderer—GPU, terminal, framebuffer, or your own. The same application code runs everywhere.
Components are function calls returning event structs. No object trees, no virtual DOM. Persistent widget state (focus, cursor, scroll) lives in a fixed-size hash table—no allocation during frames.
WebGL (GPU, MSDF text), TUI (ANSI terminal), Software (CPU framebuffer), TUI-WebGL (terminal grid + CRT effects). Write once, render anywhere.
Pan, zoom, polyline overlays, draggable markers, smooth zoom animation. Connects to Carta/Velo/Locus via abstract provider interface. Up to 16 independent maps.
Full tab navigation, focus management, text selection, clipboard integration. Focusable registry with 64-element capacity. Accessibility built in, not bolted on.
┌─────────────────────────────────────────────────┐ │ Application Code │ │ if (cs_button(id, "Save", NULL).clicked) │ │ save_document(); │ ├─────────────────────────────────────────────────┤ │ ClayShards Layer (C, ~10K lines) │ │ • Focus / hover / click state machine │ │ • Widget state store (256-slot hash table) │ │ • Hit testing with z-index layers │ │ • Tab navigation (64 focusables/frame) │ │ • Map: pan, zoom, overlays, drag markers │ ├─────────────────────────────────────────────────┤ │ Clay Layout Engine (vendor/clay/) │ │ • Declarative CLAY() macro blocks │ │ • Flexbox-like layout solving │ │ • Render command array generation │ ├──────────┬──────────┬───────────┬───────────────┤ │ WebGL │ TUI │ Software │ TUI-WebGL │ │ GPU │ ANSI │ CPU │ CRT FX │ │ MSDF │ box-draw │ sh_render │ scanlines │ └──────────┴──────────┴───────────┴───────────────┘
1. cs_frame_begin() ← Reset per-frame state (hover, click)
2. Clay_BeginLayout() ← Start layout pass
3. cs_button(...) ← Components add Clay elements + check input
cs_input(...)
cs_map_begin(...)
cs_map_end()
4. Clay_EndLayout() ← Solve layout, emit render commands
5. cs_frame_end(dt) ← Update timers, consume pending input
6. renderer.render(commands) ← Backend draws to screenClayShards never calls drawing functions. It produces Clay_RenderCommandArray and lets the renderer translate to pixels, cells, or whatever the backend needs. This is the key insight: interaction logic and rendering are completely separate concerns.
| Widget | Function | Returns | Features |
|---|---|---|---|
| Button | cs_button() | CsButtonResult | 5 variants (primary, secondary, danger, ghost, default). Hover + click. |
| Text Input | cs_input() | CsInputResult | Cursor, selection, placeholder, clipboard. Focus binding. |
| Checkbox | cs_checkbox() | CsCheckboxResult | Boolean toggle with label. |
| Toggle | cs_toggle() | CsToggleResult | Sliding knob, on/off with label placement. |
| Slider | cs_slider() | CsSliderResult | Numeric range, keyboard + drag, value display. |
| Dropdown | cs_dropdown() | CsDropdownResult | Selection list, keyboard nav, z-index layering. |
| Scroll | CS_SCROLL() | CsScrollInfo | Macro-based container. Wheel, drag, momentum. |
| Map | cs_map() | CsMapResult | Slippy map, polylines, markers, smooth zoom. |
/* Immediate mode: call = render + check */
CsButtonResult r = cs_button(
CS_ID("save"), "Save", NULL
);
if (r.clicked) {
save_document();
}
if (r.hovered) {
show_tooltip("Ctrl+S");
}
/* You own the buffer */
static char name[128] = "";
static int name_len = 0;
CsInputResult r = cs_input(
CS_ID("name"),
name, &name_len, sizeof(name),
NULL /* default style */
);
if (r.submitted) {
process_name(name);
}
/* Map with overlays */
static double lat = 47.5, lon = 19.0;
static int zoom = 12;
CsMapResult r = cs_map_begin(
CS_ID("fleet-map"), &lat, &lon, &zoom,
800, 600, NULL
);
/* Add route polyline */
CsPolylineStyle route_style = { .color = CS_COLOR_BLUE_500, .width = 3.0 };
cs_polyline(CS_ID("route"), route_points, route_count, &route_style);
/* Add draggable markers */
CsMarkerStyle marker_style = { .color = CS_COLOR_RED_500, .radius = 8, .draggable = true };
cs_marker(CS_ID("depot"), depot_lat, depot_lon, &marker_style);
cs_map_end();
if (r.panned) printf("Map moved to (%.4f, %.4f)\n", lat, lon);
if (r.clicked) printf("Clicked at (%.4f, %.4f)\n", r.click_lat, r.click_lon);GPU-accelerated rendering with MSDF (Multi-channel Signed Distance Field) text for crisp fonts at any size. Tile cache for map rendering. Handles keyboard routing, clipboard, and cursor rendering in JavaScript.
Character-cell rendering via ANSI escape sequences. Auto-detects color mode (16, 256, true color). Box-drawing styles: ASCII, light, heavy, double, rounded. Differential output—only changed cells are written. 16-byte cell format bridges to TUI-WebGL.
CPU-based framebuffer rendering using shared library primitives (sh_render, sh_font). Platform backends for macOS (Cocoa/done), X11, Win32, Wayland (planned). Headless mode for testing.
Renders the TUI character grid via WebGL with retro CRT post-processing. Scanlines, screen curvature, vignette, chromatic aberration, phosphor glow. Five presets from subtle to arcade plus amber and green phosphor modes.
| CRT Preset | Scanlines | Vignette | Chromatic Aberration |
|---|---|---|---|
| off | — | — | — |
| subtle | 0.1 | 0.1 | 0.0005 |
| retro | 0.3 | 0.2 | 0.001 |
| arcade | 0.5 | 0.3 | 0.002 |
| amber / green | 0.3 | 0.2 | 0.001 + phosphor color |
256-slot hash table with FNV-1a hashing and linear probing. Each slot stores cursor position, selection, scroll offset, blink timer, and open/closed state. No allocation during frames—slots are allocated once and reused. Lazy initialization on first access.
Single focused_id per thread. Components register as focusable during layout; tab order follows registration order. cs_focus_next() / cs_focus_prev() cycle with wraparound. Text inputs bind their buffer to active_text when focused, enabling keyboard handlers to insert characters.
Components register hit targets with bounding box and z-index. cs_hit_test(x, y) returns the topmost target. Z-index layers: CS_Z_BASE (0), CS_Z_DROPDOWN (100)—dropdowns render above buttons without DOM tricks.
All state uses thread-local storage (_Thread_local / __thread). Each thread gets independent focus, widget state, and input routing. Caveat: Clay’s layout engine uses global state, so all UI work must happen on one thread.
Honest assessment. ClayShards covers the OTTO fleet dashboard use case well: maps, forms, data tables. It is not a general-purpose UI framework—no tree views, no rich text editor, no accessibility labels (beyond tab navigation). The widget set is intentionally small and focused. If you need complex UI, use React. If you need the same code in WASM, native, and terminal—ClayShards.
clayshards/
├── clay-shards/ # Core library (C, ~10K lines)
│ ├── include/
│ │ ├── cs_common.h # Focus, input, colors, allocators
│ │ ├── cs_button.h # Button component
│ │ ├── cs_input.h # Text input component
│ │ ├── cs_checkbox.h # Checkbox component
│ │ ├── cs_toggle.h # Toggle switch
│ │ ├── cs_slider.h # Slider component
│ │ ├── cs_dropdown.h # Dropdown/select
│ │ ├── cs_scroll.h # Scroll container
│ │ ├── cs_map.h # Slippy map + overlays
│ │ └── cs_map_provider.h # Route/search provider interface
│ ├── src/ # Implementations
│ └── tests/test_clay_shards.c # ~120 tests
│
├── clay-shards-webgl/ # WebGL renderer (JS, ~2.7K lines)
│ ├── renderer.js # ClayRenderer class
│ ├── shaders.js # GLSL sources
│ ├── font.js # MSDF font loading
│ ├── map-tiles.js # Tile cache + renderer
│ ├── map-overlays.js # Polyline/marker rendering
│ └── keyboard.js # Input routing to WASM
│
├── clay-shards-tui/ # TUI renderer (C, ~1.5K lines)
│ ├── include/cs_tui.h # Terminal renderer API
│ ├── src/cs_tui.c # ANSI rendering
│ └── tests/test_tui.c # ~50 tests
│
├── clay-shards-soft/ # Software renderer (C, ~1K lines)
│ ├── include/cs_soft.h # CPU framebuffer API
│ ├── src/cs_soft.c # Rendering + platform backends
│ └── tests/test_soft.c # ~100 tests
│
└── clay-shards-tui-webgl/ # TUI-WebGL renderer (JS, ~2.2K lines)
├── tui-renderer.js # Character grid rendering
├── crt-effects.js # Post-processing pipeline
├── font-atlas.js # Procedural font atlas
└── shaders.js # CRT GLSL shaders# Build core library + tests cd clayshards/clay-shards && make # Run tests cd clayshards/clay-shards && make test # ~120 core tests cd clayshards/clay-shards-tui && make test # ~50 TUI tests cd clayshards/clay-shards-soft && make test # ~100 software tests # WASM build (requires Emscripten) cd clayshards/clay-shards && make wasm # TUI demo in browser (WebGL + CRT) cd clayshards/clay-shards-tui-webgl && make tui-demo-serve # Opens :8000 with CRT-rendered terminal UI
#include "cs_common.h"
#include "cs_button.h"
#include "cs_input.h"
void frame(float dt) {
cs_frame_begin();
Clay_BeginLayout();
/* Your UI */
CLAY(CLAY_LAYOUT({ .direction = CLAY_TOP_TO_BOTTOM })) {
static char buf[64] = "";
static int len = 0;
cs_input(CS_ID("search"), buf, &len, sizeof(buf), NULL);
if (cs_button(CS_ID("go"), "Search", NULL).clicked) {
do_search(buf);
}
}
Clay_EndLayout();
cs_frame_end(dt);
/* Pass Clay_RenderCommandArray to your renderer */
}Immediate mode eliminates synchronization: there’s no widget tree to keep in sync with your data model. Components are function calls—if the data says “show 5 rows,” you call the component 5 times. No add/remove/update API, no lifecycle callbacks, no stale state. The persistent state store handles the few things that do persist (cursor position, scroll offset).
Clay (vendor/clay/) is a layout engine: you describe elements with CLAY() macros and it solves flexbox-like layout. ClayShards adds interaction: focus management, keyboard routing, hit testing, widget state, and the component library. Clay handles “where things go.” ClayShards handles “what happens when you click them.”
Yes. After Clay_EndLayout(), iterate Clay_RenderCommandArray. Each command has a type (rectangle, text, scissor, image) with position, size, color, corner radius. Translate to your backend’s drawing primitives. The four included renderers are reference implementations.
The map component (cs_map.h) handles interaction in C (pan, zoom, marker drag). It exposes overlay data via accessor functions that the JavaScript renderer reads. The WebGL renderer fetches OSM tiles, renders them with overlays on a canvas. The provider interface abstracts tile sources—connect to Carta API, external tile servers, or embedded WASM data.
The TUI-WebGL renderer runs a post-processing pipeline on the character grid: scanlines, barrel distortion, vignette, chromatic aberration. Five presets (off/subtle/retro/arcade) plus amber and green phosphor modes. It’s a cosmetic layer—the underlying TUI renderer is the same ANSI-output code that runs in a real terminal.
No. ClayShards is intentionally low-level and immediate-mode. For the OTTO platform, this is a feature: the same C code compiles to native desktop, WASM browser, and embedded targets without a JavaScript runtime. If you need component lifecycle and virtual DOM, use React—and call OTTO’s APIs from JavaScript instead.