# Clay: a C UI layout library that outputs render commands, not pixels

> Clay is a single-header layout engine for C that computes flex-box-like layouts and hands your renderer a sorted list of drawing primitives. It is aimed at people who already own a renderer and want layout without a widget toolkit.

**nicbarker/clay** — High performance UI layout library in C.

- Repository: https://github.com/nicbarker/clay
- Website: https://nicbarker.com/clay
- Stars: 18,197 · Forks: 726
- Language: C
- License: Zlib
- Published: 2026-09-21 · Updated: 2026-09-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/nicbarker-clay

## The problem Clay solves, and who it is for

Most UI code in C ends up split between two jobs that keep interfering: deciding where things go, and drawing them. Immediate-mode GUI libraries bundle both. Clay takes only the first job. It computes positions and sizes for a tree of elements and returns a sorted array of render commands, which your existing renderer consumes. The README describes it as "a high performance 2D UI layout library" and lists renderer agnosticism as a major feature, with output that "can be easily composited in any 3D engine, and even compiled to HTML".

The audience is narrow and specific. You are writing a game, a tool, an emulator front end or an embedded interface in C or C++, you already have a draw call path, and you do not want a full GUI framework sitting between you and the GPU. The examples directory confirms this: there are demos for raylib, SDL2, SDL3, GLES3 with GLFW, sokol, cairo PDF rendering, win32 GDI, termbox2 in a terminal, and a Playdate project. Clay is not competing with Qt or Dear ImGui on widgets. It is competing with the hand-rolled layout arithmetic you would otherwise write yourself.

One consequence is worth stating plainly. Clay does not handle windows, input devices or text shaping. The README says so directly: "screenWidth and screenHeight will need to come from your environment, Clay doesn't handle window related tasks". You supply the measure-text callback, the pointer state and the scroll deltas. That is the price of the small footprint.

## How the layout pass actually works

A frame in Clay has a fixed shape. You call Clay_BeginLayout(), declare elements with the CLAY() macro, and call Clay_EndLayout(deltaTime) to get a Clay_RenderCommandArray. The deltaTime argument feeds the transition API, which is how layout animations are expressed. Everything between those two calls is a declarative tree that looks like JSX flattened into C macros, and ordinary C control flow works inside it: the README example runs a for loop that calls a SidebarItemComponent() five times.

Elements are described by a Clay_ElementDeclaration struct. Sizing uses CLAY_SIZING_GROW, CLAY_SIZING_FIXED and, per the v0.14 release notes, aspect ratio elements. Padding uses CLAY_PADDING_ALL, direction uses CLAY_TOP_TO_BOTTOM, and alignment uses constants such as CLAY_ALIGN_Y_CENTER. Because these are plain structs, they can be declared statically at file scope and reused, which is what the README does with sidebarItemConfig. A reusable component is just a function.

Memory is the part that differs most from a typical UI library. Clay uses a static arena and performs no malloc or free during layout. You ask for Clay_MinMemorySize(), allocate that much yourself, and pass it to Clay_CreateArenaWithCapacityAndMemory. The README gives a figure of roughly 3.5MB for 8192 layout elements, and notes that malloc appears in the example only because any allocator returning addressable memory of the required size will do. Two optional per-frame calls sit outside the layout block: Clay_SetLayoutDimensions for resizing, and Clay_SetPointerState plus Clay_UpdateScrollContainers for hit testing, scrolling and the debug tools. The pointer state is required for scrolling, so a layout with scroll containers is not purely a function of the element tree.

## Installing Clay and getting a first layout on screen

There is no package manager step. The README says to download or clone clay.h and include it after defining CLAY_IMPLEMENTATION in exactly one translation unit. The repository also ships a CMakeLists.txt, a cmake/ directory and a bindings/ directory, and the example projects are the practical starting point if you want a working window rather than a bare include.

Start with the single-header include. The define must come before the include, and only in one file, or you will get duplicate symbols at link time.

```c
#define CLAY_IMPLEMENTATION
#include "clay.h"
```

Next, size and create the arena. Clay_MinMemorySize() returns the byte count for the default configuration; you allocate it and hand the pointer to Clay_CreateArenaWithCapacityAndMemory. Then Clay_Initialize takes the arena, the screen dimensions and an error handler struct.

```c
uint64_t totalMemorySize = Clay_MinMemorySize();
Clay_Arena arena = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize));
Clay_Initialize(arena, (Clay_Dimensions) { screenWidth, screenHeight }, (Clay_ErrorHandler) { HandleClayErrors });
```

The error handler is not optional in practice. Clay reports problems through Clay_ErrorData, which carries an errorText Clay_String and an errorType you are expected to switch on. The README's example prints errorText.chars. Text measurement is the other callback you must write: MeasureText receives a Clay_StringSlice, a Clay_TextElementConfig and a user data pointer, and returns a Clay_Dimensions. The README warns that Clay_String chars are not guaranteed to be null terminated, so do not pass them to strlen. Its own example multiplies text.length by the font size, which the comment admits "will only work for monospace fonts".

Finally, declare a layout between Clay_BeginLayout() and Clay_EndLayout(deltaTime), then walk the returned commands. A minimal frame looks like this:

```c
Clay_BeginLayout();
CLAY(CLAY_ID("OuterContainer"), { .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)}, .padding = CLAY_PADDING_ALL(16), .childGap = 16 }, .backgroundColor = {250,250,255,255} }) {
    CLAY_TEXT(CLAY_STRING("Clay - UI Library"), { .fontSize = 24, .textColor = {255, 255, 255, 255} });
}
Clay_RenderCommandArray renderCommands = Clay_EndLayout(deltaTime);
```

What you should see is a renderCommands array whose length is the number of primitives Clay produced. Each entry has a commandType and a boundingBox. The README handles CLAY_RENDER_COMMAND_TYPE_RECTANGLE by calling DrawRectangle with the bounding box and renderData.rectangle.backgroundColor, and notes that other command types need their own cases. Text and images arrive as their own command types, so a renderer that only implements rectangles will show backgrounds and nothing else.

## What Clay will not do for you

The renderer is the biggest gap, and it is deliberate. Clay computes geometry; it does not rasterize. Every command type your UI emits needs a matching case in your switch statement, and text measurement quality is entirely your responsibility. The README's own monospace shortcut is a warning sign: proportional fonts need real measurement, and the README points to the renderers/ directory for "more advanced text measurement" rather than providing one.

There are no widgets. No button, no text field, no focus model, no accessibility tree, no dark mode toggle. A search question asks whether Clay UI has a dark mode; nothing in the README describes one, and the honest answer is that colour is whatever you put in backgroundColor. Anything interactive has to be built on top of the pointer state you feed in.

Memory is a hard ceiling rather than a soft one. The arena is sized once, and the README's figure of roughly 3.5MB for 8192 elements is a real budget line for a game or an embedded target. If your element count grows past what the arena holds, you do not get a graceful slowdown; you get an error through the handler. Sizing the arena is a design decision, not a tuning knob.

Finally, Clay is the wrong tool when the UI is the product rather than a layer inside one. If you need a settings dialog, a form and a menu bar by next week, an immediate-mode GUI library that ships widgets will get you there faster. Clay pays off when the layout is complex, the frame budget is tight, or the renderer already exists and cannot be replaced.

## How Clay differs from Dear ImGui and from writing layout by hand

Dear ImGui is the obvious comparison, and the difference is architectural rather than cosmetic. Dear ImGui is an immediate-mode GUI toolkit: it owns widgets, input handling and a rendering backend, and it draws through that backend. Clay stops at layout. It emits a sorted list of rendering primitives and leaves compositing to you. If you want a debug overlay bolted onto an existing 3D engine in an afternoon, Dear ImGui is the shorter path. If you want the layout model to be separable from the drawing code, Clay's split is the point.

The second alternative is the one most teams actually choose: hand-written layout arithmetic. That works until text wrapping, scroll containers and aspect ratio scaling arrive together, at which point you are maintaining a small flex implementation with no test suite. Clay's tests/ directory and its flex-box-like model are the argument against that path. The trade is that you inherit Clay's macro syntax and its arena sizing rules in exchange for not writing the solver.

Language bindings narrow the gap for non-C users. The repository has a bindings/ directory, and the related searches show interest in Clay from raylib, Odin and Zig users, which matches the example set: raylib demos, a cpp-project-example, and a Playdate project. The C ABI is the interface, so a binding is a thin wrapper rather than a rewrite.

## Maintenance, licence and what an upgrade costs

The repository is not archived, and the last push was on 2026-05-20. Releases have been regular rather than constant: v0.12 in October 2024, v0.13 in February 2025, and v0.14 in June 2025. The v0.13 notes mention "Simplified Config, Multi Instance Support" and v0.14 adds aspect ratio elements and better clipping, so the API has been moving between minor versions. Pinning a commit and reading the release notes before bumping is the realistic approach.

Upgrading is mostly a compile-and-fix exercise. clay.h is a single file you vendor into your tree, so there is no dependency resolution and no transitive breakage. The cost is that API changes land directly in your code: config structs, sizing macros and the CLAY() macro signature are the surfaces most likely to shift. Your MeasureText implementation and your render command switch are the two places where a version bump can change behaviour without a compiler error, since a new command type or a changed field will silently fall through.

Clay is licensed under Zlib, a permissive licence that allows use in closed-source software. That is a meaningful difference from copyleft UI libraries for anyone shipping a commercial game or a proprietary tool. This is a description of the licence identifier, not legal advice; if your organisation has a policy on attribution notices, check the LICENSE.md file in the repository, which is the authoritative text.

## Conclusion

Adopt Clay if you already have a rendering loop and want flex-box-style layout with text wrapping and scrolling without pulling in a widget toolkit, and if you are comfortable supplying your own text measurement, window handling and input state. Do not adopt it if you expect prebuilt buttons, focus handling or accessibility, because Clay draws nothing and ships no widgets. Before committing, verify two things against your own target: that Clay_MinMemorySize() fits your element counts inside the memory budget you can spare, and that your renderer can consume the command types your layout will actually emit, starting with CLAY_RENDER_COMMAND_TYPE_RECTANGLE and the text and image commands your UI needs.

## FAQ

### Can Clay be used with React?

Not directly. Clay is a C library with a React-like nested declarative syntax, but the README describes that syntax as an influence on its API, not as a JavaScript integration. The browser path the README does document is compiling Clay with clang to a 15kb uncompressed .wasm file.

### Does Clay have a dark mode?

Clay has no theme system. Colours come from the backgroundColor and textColor fields you set on each element declaration, so a dark mode is something you implement by choosing different colour values. The README does not document any built-in theme or mode switching.

### What are some popular UI libraries for C?

The material only covers Clay, which is a layout library rather than a full UI toolkit: it outputs render commands and ships no widgets. Its own examples pair it with raylib, SDL2, SDL3, GLES3, sokol, cairo, win32 GDI and termbox2, which gives a sense of the rendering libraries it is designed to sit alongside.

## Sources

- [License: Zlib](https://github.com/nicbarker/clay/blob/main/LICENSE)
- [nicbarker/clay on GitHub](https://github.com/nicbarker/clay)
- [Project website](https://nicbarker.com/clay)
- [README](https://github.com/nicbarker/clay/blob/main/README.md)
- [Releases](https://github.com/nicbarker/clay/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/nicbarker-clay
