Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Augani/kael/llms.txt

Use this file to discover all available pages before exploring further.

Rendering in Kael is declarative and frame-oriented. Each frame, Kael calls render() on the root view of every dirty window. That method returns a tree of elements — lightweight value types that describe what should appear on screen. Kael then runs the tree through a layout pass powered by Taffy, and finally paints each element to the GPU. Because the entire element tree is rebuilt and dropped every frame, elements are cheap to construct and you never have to worry about stale references.

The Render trait

Any entity that represents a window’s root (or a subtree within it) implements the Render trait:
pub trait Render: 'static + Sized {
    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement;
}
Your render method receives a mutable reference to your entity’s state and a Context<Self> that gives you access to the full app. It must return something that implements IntoElement — typically a call to div() or another built-in element function.
use kael::{Context, IntoElement, Render, Window, div, prelude::*};

struct MyView {
    label: String,
}

impl Render for MyView {
    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
        div()
            .flex()
            .items_center()
            .child(self.label.clone())
    }
}
render takes &mut self, so you can freely read and mutate your entity’s state when producing the element tree. The Context<Self> gives you access to other entities, globals, and window APIs.

IntoElement and the element tree

The IntoElement trait is the core conversion interface. Anything that can be turned into an Element implements it:
pub trait IntoElement: Sized {
    type Element: Element;

    fn into_element(self) -> Self::Element;
    fn into_any_element(self) -> AnyElement;
}
Most of the time you work with concrete types returned by the built-in element constructors. When you need to erase the concrete type — for example, when building a Vec of heterogeneous children — call .into_any_element() to get an AnyElement.

AnyElement for type-erased elements

AnyElement is the dynamically-typed envelope that Kael uses internally to store a mixed element tree. You rarely need to construct one manually; ParentElement::child() and ParentElement::children() call into_any_element() for you. It is, however, the type you work with when implementing your own low-level Element.
fn into_any(self) -> AnyElement {
    AnyElement::new(self)
}

Composing children

Any element that implements ParentElement exposes .child() and .children() for building a subtree:
pub trait ParentElement {
    fn child(self, child: impl IntoElement) -> Self;
    fn children(self, children: impl IntoIterator<Item = impl IntoElement>) -> Self;
}
These methods consume self and return Self, making them suitable for chaining:
div()
    .flex_col()
    .child(
        div().text_sm().child("First item")
    )
    .children(
        items.iter().map(|item| div().child(item.label.clone()))
    )

Built-in elements

Kael ships a rich set of ready-to-use element constructors. The most common ones are:
ConstructorDescription
div()The universal container element, analogous to HTML <div>
label(text)A styled text label
img(source)An image element supporting remote URLs and local assets
svg(path)An SVG renderer element
canvas(paint_fn)An imperative GPU canvas for custom drawing
All of these return types that implement both Element and Styled, so you can chain layout and style methods directly:
img(asset_url)
    .w(px(128.0))
    .h(px(128.0))
    .rounded_lg()

The Element trait

When you need full control over layout and painting — for example, to implement a code editor or a custom chart — you can implement Element directly:
pub trait Element: 'static + IntoElement {
    type RequestLayoutState: 'static;
    type PrepaintState: 'static;

    fn id(&self) -> Option<ElementId>;
    fn request_layout(
        &mut self,
        id: Option<&GlobalElementId>,
        inspector_id: Option<&InspectorElementId>,
        window: &mut Window,
        cx: &mut App,
    ) -> (LayoutId, Self::RequestLayoutState);
    fn prepaint(/* ... */) -> Self::PrepaintState;
    fn paint(/* ... */);
}
The three-phase protocol (request_layoutprepaintpaint) matches the browser’s layout model. In request_layout you describe your size constraints to Taffy and return any state you need later. In prepaint you commit hitboxes and prepare paint state. In paint you issue draw calls to the GPU.
Implementing Element directly is an escape hatch for advanced use cases. For most UI components, use RenderOnce with #[derive(IntoElement)] instead.

RenderOnce for reusable components

RenderOnce is the idiomatic way to build reusable, stateless UI components. Unlike Render (which holds state across frames inside an entity), RenderOnce consumes self and is rebuilt on every frame:
use kael::{IntoElement, RenderOnce, Window, App, div};

#[derive(IntoElement)]
struct Badge {
    label: String,
    color: Hsla,
}

impl RenderOnce for Badge {
    fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
        div()
            .px(px(8.0))
            .py(px(4.0))
            .rounded()
            .bg(self.color)
            .child(self.label)
    }
}
The #[derive(IntoElement)] macro generates the IntoElement implementation that wraps your component in a Component<Badge> and routes it through the element tree.

The #[derive(Render)] macro

For very simple views that don’t need full manual control, the #[derive(Render)] macro generates a boilerplate Render implementation. It is most useful for views whose render output is computed entirely from their fields with a single expression.

The render cycle and dirty tracking

Kael does not re-render every view on every frame. Instead it tracks which entities have been marked dirty — either because cx.notify() was called or because a subscribed-to entity changed — and only re-renders the views that actually need updating. This dirty-tracking approach means Kael uses 0% CPU at idle: no entity is dirty, so no rendering occurs.
1

Entity marks itself dirty

Your entity calls cx.notify() from within an update, or Kael marks it dirty automatically when a subscribed entity emits an event.
2

Window schedules a repaint

The window associated with the dirty view is scheduled for a repaint on the next display refresh.
3

render() is called

Kael calls render() on the dirty entity, producing a fresh element tree. The previous frame’s tree is discarded.
4

Layout and paint

Taffy computes the layout. Kael issues GPU draw calls for each painted element. The frame is presented to the display.

Caching subtrees

For expensive subtrees that rarely change, you can opt into frame-level caching with AnyView::cached(). When a cached view has not been notified since the last frame, Kael recycles its previous layout and paint output:
let view: AnyView = entity.into();
view.cached(StyleRefinement::default())
The cache is invalidated automatically whenever cx.notify() is called on that entity, or whenever Window::refresh() is triggered.

Build docs developers (and LLMs) love