Props & State
Props
Props are typed inputs set by the parent. They are immutable within the component.
component Display {
prop value: *u8;
prop size: i64 = 16;
prop bold: bool = false;
}The parent sets props at instantiation:
Display { value: "Hello"; size: 24; }
Display { value: count_text; } // uses default size and boldRules:
- Props with defaults are optional at instantiation
- Props without defaults are required (compiler error if missing)
- Props are read-only inside the component
- Props can be any type: primitives, structs, pointers, function pointers,
color - Props are style-targetable —
#stylecan override default values
Props and #style
Props can be set from #style blocks, overriding defaults without the parent explicitly passing them:
component Button {
prop bg: color = #444444;
prop fg: color = #FFFFFF;
prop label: *u8;
}#style
Button {
bg: #336699;
fg: #FCFDFF;
:hovered { bg: #4477AA; }
:active { bg: #225588; }
}The #style values act as overrides to the prop defaults. The parent can still explicitly set a prop at instantiation, which takes priority over the style.
Reactive state (var)
Mutable var declarations inside a component are reactive state. Any mutation marks the component dirty for re-render on the next frame:
component Counter {
var count: i64 = 0;
var count_text: *u8 = "0";
fn increment() {
count = count + 1;
count_text = str.i64_to_cstr(count);
}
}State initializers run once at component creation and can reference props.
Auto-generated setters
For every var field, the compiler generates a set_<name> method that stores the value and marks dirty:
component Slider {
var value: f64 = 0.0;
// compiler generates: fn set_value(val: f64) { value = val; /* mark dirty */ }
}These are useful for binding to events:
widgets.TextInput { value: query; on_change: set_query; }If you define fn set_<name> manually, the compiler skips auto-generation for that field.
Dirty marking
The compiler inserts dirty flag writes after every var assignment inside a component method. Multiple mutations in one handler are idempotent — the component is repainted at most once per frame:
- Event handler executes (may mutate state multiple times)
- Handler returns
- Frame tick: runtime walks dirty components, repaints dirty regions
- Clear dirty flags
Interaction state (state)
state declares boolean flags specifically for interaction styling. Unlike var, these are targetable from #style via :name pseudo-selectors:
component Button {
state hovered: bool = false;
state active: bool = false;
state disabled: bool = false;
fn handle_pointer(ev: *ui.PointerEvent) {
if (ev.kind == ui.PointerKind.Enter) { hovered = true; }
if (ev.kind == ui.PointerKind.Leave) { hovered = false; }
if (ev.kind == ui.PointerKind.Down) { active = true; }
if (ev.kind == ui.PointerKind.Up) { active = false; }
}
}Rules:
statefields must bebool- Mutations mark dirty (same as
var) - Targetable from
#stylewith:namepseudo-selectors
#style
Button {
bg: #444444;
:hovered { bg: #555555; }
:active { bg: #333333; }
:disabled { bg: #222222; fg: #666666; }
}Here bg and fg target props (overriding their values), while :hovered, :active, and :disabled target state fields (conditional styling when that flag is true). The compiler verifies each :name matches a state declaration — unmatched selectors are a compile error.
Props vs var vs state
prop | var | state | |
|---|---|---|---|
| Set by | Parent / #style | Component | Component |
| Mutable inside | No | Yes | Yes |
| Marks dirty | When parent changes | On any mutation | On any mutation |
| Type | Any | Any | bool only |
| Style-targetable | Yes (value override) | No | Yes (:name selector) |
| Auto-setter | No | Yes (set_<name>) | No |
Last modified: