Skip to content

Components

Components are reusable UI building blocks with typed props, content slots, outward signals, and optional state per instance.

Defining and using a component

component "Card" {
    prop "title" type="string" default=""
    slot "body"

    vbox {
        bg-color "#2D2D2D"; bg-opa 255; radius 12; padding 16; gap 8
        label { text bind="prop.title"; text-color "#888888" }
        render-slot "body"
    }
}

Card {
    title "Sensors"
    body {
        label "Temperature: 22°C"
        label "Humidity: 65%"
    }
}

Props

Props are the component’s typed inputs:

component "StatCard" {
    prop "title" type="string" required=#true
    prop "value" type="int" default=0
    prop "color" type="color" default="#FFFFFF"
    // ...
}

The shorthand annotation form also works: prop "idx" (int) 0.

Props without a default are required. The validator reports an error if one is missing at the call site, at load time.

Reactive prop access

Inside a component template, prop.name gives typed, reactive access in any expression context:

component "Gauge" {
    prop "value" type="int" default=0
    arc { value bind="prop.value" }
}

// Forward a live state binding. The gauge follows app.temperature
Gauge { value bind="app.temperature" }

When the caller passes bind=, the prop updates automatically as that state changes. Components compose reactively without any wiring.

Slots

Slots let callers inject entire widget subtrees:

component "Section" {
    prop "title" type="string" default="Section"
    slot "content"
    slot "footer"

    vbox {
        label { text bind="prop.title" }
        render-slot "content"
        render-slot "footer"
    }
}

Section {
    title "Devices"
    content {
        button "Light"
        button "Lock"
    }
    footer {
        label "Last updated: 5m ago"
    }
}

Slot content evaluates in the caller’s scope. Bindings inside a slot reference the caller’s state, not the component’s.

Property aliases

Aliases forward a call site binding directly to an inner widget, enabling two-way model= bindings through components:

component "SettingSlider" {
    prop "title" type="string" default="Setting"
    prop "value" type="alias" target="sl.value"

    hbox {
        label { text bind="prop.title" }
        slider id="sl" { min 0; max 100 }
    }
}

SettingSlider {
    title "Brightness"
    value model="app.brightness"   // forwarded to the inner slider
}

Signals

Signals let components communicate events outward:

component "DeviceRow" {
    prop "name" type="string"
    signal "on-toggle"

    hbox {
        label { text bind="prop.name" }
        switch {
            on-change { emit "on-toggle" }
        }
    }
}

DeviceRow {
    name "Lights"
    on-toggle { set "lights" expr="!app.lights" }
}

Instance state

Components can hold state that is independent per instantiation, referenced with self.:

component "TodoItem" {
    prop "text" type="string"
    state { done #false }

    hbox {
        checkbox { checked model="self.done" }
        label {
            text bind="prop.text"
            text-color bind="self.done ? '#666' : '#FFF'"
        }
    }
}

Each TodoItem tracks its own done, with no key management or external bookkeeping.

Single-file components

A .ui.kdl file with a single widget root and no app block is automatically a component. It supports prop, slot, signal, state, aliases, and its own import for shared styles:

// components/Card.ui.kdl
prop "title" type="string" default=""
slot "body"

import "../styles.ui.kdl"

vbox {
    style "card"
    label { text bind="prop.title"; style "subtitle" }
    render-slot "body"
}

Single-file components can also be rendered standalone for preview and testing, using their declared prop defaults.

Delegates

Delegates are components designed for virtualized lists. They auto-receive item and index props:

delegate "ItemCard" {
    hbox {
        label { text bind="item.name" }
        label { text bind="'' + index" }
    }
}

list-view "rooms" as="item" delegate="ItemCard" {
    item-height 64
}

See Advanced → Virtualized list-view.