Clay: a flex-box style UI auto layout library in C
Clay (short for C Layout) is a flex-box style UI auto layout library in C, with declarative syntax and microsecond performance.
Features
- Microsecond layout performance
- Flex-box like layout model for complex, responsive layouts including text wrapping, scrolling containers and aspect ratio scaling
- Single ~2k LOC clay.h file with zero dependencies (including no standard library)
- Wasm support: compile with clang to a 15kb uncompressed .wasm file for use in the browser
- Static arena based memory use with no malloc / free, and low total memory overhead (e.g. ~3.5mb for 8192 layout elements).
- React-like nested declarative syntax
- Renderer agnostic: outputs a sorted list of rendering primitives that can be easily composited in any 3D engine, and even compiled to HTML (examples provided)
Take a look at the clay website for an example of clay compiled to wasm and running in the browser, or others in the examples directory.
An example GUI application built with clay
Quick Start
Download or clone clay.h and include it after defining CLAY_IMPLEMENTATION in one file.
// Must be defined in one file, _before_ #include "clay.h"
#define CLAY_IMPLEMENTATION
#include "clay.h"
Ask clay for how much static memory it needs using Clay_MinMemorySize(), create an Arena for it to use with Clay_CreateArenaWithCapacityAndMemory(size, void *memory), and initialize it with Clay_Initialize(arena, dimensions).
// Note: malloc is only used here as an example, any allocator that provides
// a pointer to addressable memory of at least totalMemorySize will work
uint64_t totalMemorySize = Clay_MinMemorySize();
Clay_Arena arena = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize));
Clay_Initialize(arena, (Clay_Dimensions) { screenWidth, screenHeight });
Provide a MeasureText(text, config) function pointer with Clay_SetMeasureTextFunction(function) so that clay can measure and wrap text.
// Example measure text function
static inline Clay_Dimensions MeasureText(Clay_String *text, Clay_TextElementConfig *config) {
// Clay_TextElementConfig contains members such as fontId, fontSize, letterSpacing etc
// Note: Clay_String->chars is not guaranteed to be null terminated
}
// Tell clay how to measure text
Clay_SetMeasureTextFunction(MeasureText);
Optional - Call Clay_SetLayoutDimensions(dimensions) if the window size of your application has changed.
// Update internal layout dimensions
Clay_SetLayoutDimensions((Clay_Dimensions) { screenWidth, screenHeight });
Optional - Call Clay_SetPointerState(pointerPosition, isPointerDown) if you want to use mouse interactions.
// Update internal pointer position for handling mouseover / click / touch events
Clay_SetPointerState((Clay_Vector2) { mousePositionX, mousePositionY }, isMouseDown);
Optional - Call Clay_UpdateScrollContainers(enableDragScrolling, scrollDelta, deltaTime) if you want to use clay's built in scrolling containers.
// Update internal pointer position for handling mouseover / click / touch events
Clay_UpdateScrollContainers(true, (Clay_Vector2) { mouseWheelX, mouseWheelY }, deltaTime);
Call Clay_BeginLayout() and declare your layout using the provided macros.
const Clay_Color COLOR_LIGHT = (Clay_Color) {224, 215, 210, 255};
const Clay_Color COLOR_RED = (Clay_Color) {168, 66, 28, 255};
const Clay_Color COLOR_ORANGE = (Clay_Color) {225, 138, 50, 255};
// Layout config is just a struct that can be declared statically, or inline
Clay_LayoutConfig sidebarItemLayout = (Clay_LayoutConfig) {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(50) },
};
// Re-useable components are just normal functions
void SidebarItemComponent() {
CLAY(CLAY_LAYOUT(sidebarItemLayout), CLAY_RECTANGLE({ .color = COLOR_ORANGE })) {}
}
// An example function to begin the "root" of your layout tree
Clay_RenderCommandArray CreateLayout() {
Clay_BeginLayout();
// An example of laying out a UI with a fixed width sidebar and flexible width main content
CLAY(CLAY_ID("OuterContainer"), CLAY_LAYOUT({ .sizing = {CLAY_SIZING_GROW(), CLAY_SIZING_GROW()}, .padding = {16, 16}, .childGap = 16 }), CLAY_RECTANGLE({ .color = {250,250,255,255} })) {
CLAY(CLAY_ID("SideBar"),
CLAY_LAYOUT({ .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_FIXED(300), .height = CLAY_SIZING_GROW() }, .padding = {16, 16}, .childGap = 16 }),
CLAY_RECTANGLE({ .color = COLOR_LIGHT })
) {
CLAY(CLAY_ID("ProfilePictureOuter"), CLAY_LAYOUT({ .sizing = { .width = CLAY_SIZING_GROW() }, .padding = {16, 16}, .childGap = 16, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER }), CLAY_RECTANGLE({ .color = COLOR_RED })) {
CLAY(CLAY_ID("ProfilePicture"), CLAY_LAYOUT({ .sizing = { .width = CLAY_SIZING_FIXED(60), .height = CLAY_SIZING_FIXED(60) }}), CLAY_IMAGE({ .imageData = &profilePicture, .height = 60, .width = 60 })) {}
CLAY_TEXT(CLAY_STRING("Clay - UI Library"), CLAY_TEXT_CONFIG({ .fontSize = 24, .textColor = {255, 255, 255, 255} }));
}
// Standard C code like loops etc work inside components
for (int i = 0; i < 5; i++) {
SidebarItemComponent();
}
}
CLAY(CLAY_ID("MainContent"), CLAY_LAYOUT({ .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() }}), CLAY_RECTANGLE({ .color = COLOR_LIGHT })) {}
}
// ...
});
Call Clay_EndLayout() and process the resulting Clay_RenderCommandArray in your choice of renderer.
Clay_RenderCommandArray renderCommands = Clay_EndLayout();
for (int i = 0; i < renderCommands.length; i++) {
Clay_RenderCommand *renderCommand = &renderCommands.internalArray[i];
switch (renderCommand->commandType) {
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
DrawRectangle(
renderCommand->boundingBox,
renderCommand->config.rectangleElementConfig->color);
}
// ... Implement handling of other command types
}
}
The above example, rendered correctly will look something like the following:
In summary, the general order of steps is:
- Clay_SetLayoutDimensions(dimensions)
- Clay_SetPointerState(pointerPosition, isPointerDown)
- Clay_UpdateScrollContainers(enableDragScrolling, scrollDelta, deltaTime)
- Clay_BeginLayout()
- Declare your layout with the provided Element Macros
- Clay_EndLayout()
- Render the results using the outputted Clay_RenderCommandArray
Documentation
Building UI Hierarchies
Clay UIs are built using the C macro CLAY(). This macro creates a new empty element in the UI hierarchy, and supports modular customisation of layout, styling and functionality. The CLAY() macro can also be nested, similar to other declarative UI systems like HTML.
Child elements are added by opening a block: {} after calling the CLAY() macro (exactly like you would with an if statement or for loop), and declaring child components inside the braces.
// Parent element with 8px of padding
CLAY(CLAY_LAYOUT({ .padding = 8 })) {
// Child element 1
CLAY_TEXT(CLAY_STRING("Hello World"), CLAY_TEXT_CONFIG({ .fontSize = 16 }));
// Child element 2 with red background
CLAY(CLAY_RECTANGLE({ .color = COLOR_RED })) {
// etc
}
}
However, unlike HTML and other declarative DSLs, these macros are just C. As a result, you can use arbitrary C code such as loops, functions and conditions inside your layout declaration code:
// Re-usable "components" are just functions that declare more UI
void ButtonComponent(Clay_String buttonText) {
// Red box button with 8px of padding
CLAY(CLAY_LAYOUT({ .padding = { 8, 8 }}), CLAY_RECTANGLE({ .color = COLOR_RED })) {
CLAY_TEXT(buttonText, textConfig);
}
}
// Parent element
CLAY(CLAY_LAYOUT({ .layoutDirection = CLAY_TOP_TO_BOTTOM })) {
// Render a bunch of text elements
for (int i = 0; i < textArray.length; i++) {
CLAY_TEXT(textArray.elements[i], textConfig);
}
// Only render this element if we're on a mobile screen
if (isMobileScreen) {
CLAY() {
// etc
}
}
// Re-usable components
ButtonComponent(CLAY_STRING("Click me!"));
ButtonComponent(CLAY_STRING("No, click me!"));
});
Configuring Layout and Styling UI Elements
The layout of clay elements is configured with the CLAY_LAYOUT() macro.
CLAY(CLAY_LAYOUT({ .padding = {.x = 8, .y = 8}, .layoutDirection = CLAY_TOP_TO_BOTTOM })) {
// Children are 8px inset into parent, and laid out top to bottom
}
This macro isn't magic - all it's doing is wrapping the standard designated initializer syntax and adding the result to an internal array. e.g. (Clay_LayoutConfig) { .padding = { .x = 8, .y = 8 } ....
See the Clay_LayoutConfig API for the full list of options.
A Clay_LayoutConfig struct can be defined in file scope or elsewhere, and reused.
// Define a style in the global / file scope
Clay_LayoutConfig reusableStyle = (Clay_LayoutConfig) { .backgroundColor = {120, 120, 120, 255} };
CLAY(CLAY_LAYOUT(reusableStyle)) {
// ...
}
Element IDs
Clay elements can optionally be tagged with a unique identifier using CLAY_ID().
// Will always produce the same ID from the same input string
CLAY(CLAY_ID("OuterContainer"), style) {}
Element IDs have two main use cases. Firstly, tagging an element with an ID allows you to query information about the element later, such as its mouseover state or dimensions.
Secondly, IDs are visually useful when attempting to read and modify UI code, as well as when using the built-in debug tools.
To avoid having to construct dynamic strings at runtime to differentiate ids in loops, clay provides the CLAY_IDI(string, index) macro to generate different ids from a single input string. Think of IDI as "ID + Index"
// This is the equivalent of calling CLAY_ID("Item0"), CLAY_ID("Item1") etc
for (int index = 0; index < items.length; index++) {
CLAY(CLAY_IDI("Item", index)) {}
}
This ID (or, if not provided, an auto generated ID) will be forwarded to the final Clay_RenderCommandArray for use in retained mode UIs. Using duplicate IDs may cause some functionality to misbehave (i.e. if you're trying to attach a floating container to a specific element with ID that is duplicated, it may not attach to the one you expect)
Mouse, Touch and Pointer Interactions
Clay provides several functions for handling mouse and pointer interactions.
All pointer interactions depend on the function void Clay_SetPointerState(Clay_Vector2 position) being called after each mouse position update and before any other clay functions.
During UI declaration
The function bool Clay_Hovered() can be called during element construction or in the body of an element, and returns true if the mouse / pointer is over the currently open element.
// An orange button that turns blue when hovered
CLAY(CLAY_RECTANGLE(.color = Clay_Hovered() ? COLOR_BLUE : COLOR_ORANGE)) {
bool buttonHovered = Clay_Hovered();
CLAY_TEXT(buttonHovered ? CLAY_STRING("Hovered") : CLAY_STRING("Hover me!"), headerTextConfig);
}
The function void Clay_OnHover() allows you to attach a function pointer to the currently open element, which will be called if the mouse / pointer is over the element.
void HandleButtonInteraction(Clay_ElementId elementId, Clay_PointerData pointerInfo, intptr_t userData) {
ButtonData *buttonData = (ButtonData *)userData;
// Pointer state allows you to detect mouse down / hold / release
if (pointerInfo.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) {
// Do some click handling
NavigateTo(buttonData->link);
}
}
ButtonData linkButton = (ButtonData) { .link = "https://github.com/nicbarker/clay" };
// HandleButtonInteraction will be called for each frame the mouse / pointer / touch is inside the button boundaries
CLAY(CLAY_LAYOUT({ .padding = { 8, 8 }}), Clay_OnHover(HandleButtonInteraction, &linkButton)) {
CLAY_TEXT(CLAY_STRING("Button"), &headerTextConfig);
}
Before / After UI declaration
If you want to query mouse / pointer overlaps outside layout declarations, you can use the function bool Clay_PointerOver(Clay_ElementId id), which takes an element id and returns a bool representing whether the current pointer position is within its bounding box.
// Reminder: Clay_SetPointerState must be called before functions that rely on pointer position otherwise it will have no effect
Clay_Vector2 mousePosition = { x, y };
Clay_SetPointerState(mousePosition);
// ...
// If profile picture was clicked
if (mouseButtonDown(0) && Clay_PointerOver(Clay_GetElementId("ProfilePicture"))) {
// Handle profile picture clicked
}
Note that the bounding box queried by Clay_PointerOver is from the last frame. This generally shouldn't make a difference except in the case of animations that move at high speed. If this is an issue for you, performing layout twice per frame with the same data will give you the correct interaction the second time.
Scrolling Elements
Elements are configured as scrollable with the CLAY_SCROLL macro. To make scroll containers respond to mouse wheel and scroll events, two functions need to be called before BeginLayout():
Clay_Vector2 mousePosition = { x, y };
// Reminder: Clay_SetPointerState must be called before Clay_UpdateScrollContainers otherwise it will have no effect
Clay_SetPointerState(mousePosition);
// Clay_UpdateScrollContainers needs to be called before Clay_BeginLayout for the position to avoid a 1 frame delay
Clay_UpdateScrollContainers(
true, // Enable drag scrolling
scrollDelta, // Clay_Vector2 scrollwheel / trackpad scroll x and y delta this frame
float deltaTime, // Time since last frame in seconds as a float e.g. 8ms is 0.008f
);
// ...
More specific details can be found in the full Scroll API.
Floating Elements ("Absolute" Positioning)
All standard elements in clay are laid out on top of, and within their parent, positioned according to their parent's layout rules, and affect the positioning and sizing of siblings.
"Floating" is configured with the CLAY_FLOATING() macro. Floating elements don't affect the parent they are defined in, or the position of their siblings. They also have a z-index, and as a result can intersect and render over the top of other elements.
A classic example use case for floating elements is tooltips and modals.
// The two text elements will be laid out top to bottom, and the floating container
// will be attached to "Outer"
CLAY(CLAY_ID("Outer"), CLAY_LAYOUT({ .layoutDirection = TOP_TO_BOTTOM })) {
CLAY_TEXT(CLAY_ID("Button"), text, &headerTextConfig);
CLAY(CLAY_ID("Tooltip"), CLAY_FLOATING()) {}
CLAY_TEXT(CLAY_ID("Button"), text, &headerTextConfig);
}
More specific details can be found in the full Floating API.
Laying Out Your Own Custom Elements
Clay only supports a simple set of UI element primitives, such as rectangles, text and images. Clay provides a singular API for layout out custom elements:
// Extend CLAY_CUSTOM_ELEMENT_CONFIG with your custom data
#define CLAY_EXTEND_CONFIG_CUSTOM struct t_CustomElementData customData;
// Extensions need to happen _before_ the clay include
#include "clay.h"
enum CustomElementType {
CUSTOM_ELEMENT_TYPE_MODEL,
CUSTOM_ELEMENT_TYPE_VIDEO
};
// A rough example of how you could handle laying out 3d models in your UI
typedef struct t_CustomElementData {
CustomElementType type;
union {
Model model;
Video video;
// ...
};
} CustomElementData;
Model myModel = Load3DModel(filePath);
CustomElement modelElement = (CustomElement) { .type = CUSTOM_ELEMENT_TYPE_MODEL, .model = myModel }
// ...
CLAY() {
// This config is type safe and contains the CustomElementData struct
CLAY(CLAY_CUSTOM_ELEMENT({ .customData = { .type = CUSTOM_ELEMENT_TYPE_MODEL, .model = myModel } })) {}
}
// Later during your rendering
switch (renderCommand->commandType) {
// ...
case CLAY_RENDER_COMMAND_TYPE_CUSTOM: {
// Your extended struct is passed through
CustomElementData *customElement = renderCommand->config.customElementConfig->customData;
if (!customElement) continue;
switch (customElement->type) {
case CUSTOM_ELEMENT_TYPE_MODEL: {
// Render your 3d model here
break;
}
case CUSTOM_ELEMENT_TYPE_VIDEO: {
// Render your video here
break;
}
// ...
}
break;
}
}
More specific details can be found in the full Custom Element API.
Retained Mode Rendering
Clay was originally designed for Immediate Mode rendering - where the entire UI is redrawn every frame. This may not be possible with your platform, renderer design or performance constraints.
There are some general techniques that can be used to integrate clay into a retained mode rendering system:
- Clay_RenderCommand includes the uint32_t id that was used to declare the element. If unique ids are used, these can be mapped to persistent graphics objects across multiple frames / layouts.
- Render commands are culled automatically to only currently visible elements, and Clay_RenderCommand is a small enough struct that you can simply compare the memory of two render commands with matching IDs to determine if the element is "dirty" and needs to be re-rendered or updated.
For a worked example, see the provided HTML renderer. This renderer converts clay layouts into persistent HTML documents with minimal changes per frame.
Visibility Culling
Clay provides a built-in visibility-culling mechanism that is enabled by default. It will only output render commands for elements that are visible - that is, at least one pixel of their bounding box is inside the viewport.
This culling mechanism can be disabled via the use of the #define CLAY_DISABLE_CULLING directive. See Preprocessor Directives for more information.
Preprocessor Directives
Clay supports C preprocessor directives to modulate functionality at compile time. These can be set either in code using #define CLAY_DISABLE_CULLING or on the command line when compiling using the appropriate compiler specific arguments, e.g. clang -DCLAY_DISABLE_CULLING main.c ...
The supported directives are:
- CLAY_MAX_ELEMENT_COUNT - Controls the maximum number of clay elements that memory is pre-allocated for. Defaults to 8192, which should be more than enough for the majority of use cases. Napkin math is ~450 bytes of memory overhead per element (8192 elements is ~3.5mb of memory)
- CLAY_DISABLE_CULLING - Disables Visibility Culling of render commands.
- CLAY_WASM - Required when targeting Web Assembly.
- CLAY_OVERFLOW_TRAP - By default, clay will continue to allow function calls without crashing even when it exhausts all its available pre-allocated memory. This can produce erroneous layout results that are difficult to interpret. If CLAY_OVERFLOW_TRAP is defined, clay will raise a SIGTRAP signal that will be caught by your debugger. Relies on signal.h being available in your environment.
- CLAY_DEBUG - Used for debugging clay's internal implementation. Useful if you want to modify or debug clay, or learn how things work. It enables a number of debug features such as preserving source strings for hash IDs to make debugging easier.
- CLAY_EXTEND_CONFIG_RECTANGLE - Provide additional struct members to CLAY_RECTANGLE that will be passed through with output render commands.
- CLAY_EXTEND_CONFIG_TEXT - Provide additional struct members to CLAY_TEXT_CONFIG that will be passed through with output render commands.
- CLAY_EXTEND_CONFIG_IMAGE - Provide additional struct members to CLAY_IMAGE_CONFIG that will be passed through with output render commands.
- CLAY_EXTEND_CONFIG_CUSTOM - Provide additional struct members to CLAY_IMAGE_CONFIG that will be passed through with output render commands.
Bindings for non C
Clay is usable out of the box as a .h include in both C99 and C++20 with designated initializer support. There are also supported bindings for other languages, including:
Unfortunately clay does not support Microsoft C11 or C17 via MSVC at this time.
Debug Tools
Clay includes built-in UI debugging tools, similar to the "inspector" in browsers such as Chrome or Firefox. These tools are included in clay.h, and work by injecting additional render commands into the output Clay_RenderCommandArray.
As long as the renderer that you're using works correctly, no additional setup or configuration is required to use the debug tools.
To enable the debug tools, use the function Clay_SetDebugModeEnabled(bool enabled). This boolean is persistent and does not need to be set every frame.
The debug tooling by default will render as a panel to the right side of the screen, compressing your layout by its width. The default width is 400 and is currently configurable via the direct mutation of the internal variable Clay__debugViewWidth, however this is an internal API and is potentially subject to change.
The official Clay website with debug tooling visible