Component Overview
Components are built into the language. The compiler understands them at the IR level and generates vtable dispatch, dirty marking, auto-setters, and efficient state updates — no runtime framework overhead.
What is a component?
A component is a self-contained unit with:
- Props — typed inputs from the parent (immutable inside)
- Vars — mutable reactive state (mutations mark dirty)
- State — boolean interaction flags (targetable from
#style) - Events — typed single-handler callbacks
- Slots — named insertion points for child content
- Dispatch — vtable slots for polymorphic method calls
- Methods — regular functions
- View — a declarative tree of child components
A simple component
tape
@profile(t2);
import io from "io";
import ui from "ui";
import str from "str";
import widgets from "widgets";
component Counter {
var count: i64 = 0;
var count_text: *u8 = "0";
fn increment() {
count = count + 1;
count_text = str.i64_to_cstr(count);
}
fn decrement() {
count = count - 1;
count_text = str.i64_to_cstr(count);
}
view {
ui.Column {
widgets.Label { text: count_text; fg: #333333; size: 24; }
ui.Row {
widgets.Button { label: "+"; on_click: increment; }
widgets.Button { label: "-"; on_click: decrement; }
}
}
}
}
pub fn main() -> i32 {
let app = Counter {};
ui.run(app as *u8, 400, 200, "Counter");
return 0;
}Visual vs non-visual
Components don’t have to be visual. A non-visual component manages state and events without a view tree:
tape
component Timer {
prop interval: u64;
var running: bool = false;
event on_tick();
fn on_mount() {
running = true;
}
}Visual components add a view { } block and typically use @tape { ui.drawable(); } to opt into the paint dispatch:
tape
component Badge {
@tape { ui.drawable(); }
prop label: *u8;
prop bg: color = #333333;
fn paint(ctx: *u8, x: i32, y: i32, w: i32, h: i32) {
gfx.fill_rect(ctx, x, y, w, h, bg);
gfx.draw_text_utf8(ctx, null, null, 14, x + 4, y + 12, label, #FFFFFF);
}
}Instantiation
Components are instantiated with struct-literal-like syntax, binding props and events:
tape
pub fn main() -> i32 {
let app = Counter {};
ui.run(app as *u8, 400, 200, "Counter");
return 0;
}In view trees:
tape
view {
widgets.Button { label: "+"; on_click: increment; }
}Why built-in?
The compiler generates:
- Vtable dispatch — polymorphic
paint/handle_pointer/layoutcalls through function pointer tables - Dirty marking — bit-per-var flags set on mutation, checked once per frame
- Auto-setters —
set_<var>methods generated unless manually defined on_mountcalls — invoked automatically after instantiation and view initialization- View tree codegen —
__init_viewfunctions that build the child tree
Last modified: