Author a component tree

This guide shows you how to build a spaday UI from typed components: setting props, nesting children, laying out with shell components, and serializing the result. To attach behavior, see Add behavior and reactivity.

Use a built-in component

WebAwesome components are generated as typed classes. Import one and set its attributes as keyword arguments:

from spaday.components.webawesome import WaButton

WaButton(variant="brand", size="large")

Every attribute is a typed keyword (variant: Optional[Literal["brand", "neutral", ...]], disabled: Optional[bool], …), so a typo or a wrong type is an error at authoring time. A prop you don’t pass is omitted, so the element keeps its own default and update patches stay minimal.

To use a component library other than WebAwesome, generate its classes from a manifest.

Nest children into slots

Compose a tree with .child(...) for the default slot and .child_in("slot", ...) for a named one:

from spaday.components.webawesome import WaCard, WaButton, WaSwitch

WaCard().child_in("header", WaButton(variant="brand")).child(WaSwitch())

.child returns the parent, so calls chain. Children are other components (or a raw element — below).

Set text

.text(...) sets a leaf’s text content — use it for labels, not alongside child nodes:

WaButton(variant="brand").text("Save")

Lay out with shell components

spaday does not expose div. Compose layout from the spa-* shell components, which carry their own encapsulated layout:

from spaday.components.shell import App, Nav, Body, Gutter, Main, Footer, Stack, Row, Toolbar

App().child(Nav().child(...)).child(Body().child(Gutter().child(...)).child(Main().child(...)))

Stack stacks children vertically, Row lays them horizontally, Toolbar is a control strip; App / Nav / Body / Gutter / Main / Footer are the page shell.

Reach for a raw element

For text or a structural tag a typed class doesn’t cover, use element:

from spaday import element

element("strong").text("Settings")
element("a", href="https://example.com").text("docs")

A trailing underscore on a prop name is stripped, so reserved words work: element("label", for_="x").

Set a prop a typed class doesn’t expose

.prop(name, value) is the escape hatch for an attribute the generated class doesn’t have (a custom attribute, style, id, …):

WaButton().prop("id", "save").prop("style", "margin-left:auto")

Key for stable updates

Give a node a stable key so the diff engine reconciles it by identity across updates (so a reordered list moves live elements instead of rebuilding them):

WaSwitch().key("lamp")

Serialize

.to_node() returns the JSON-ready node dict; .to_json() returns its string form. This is the wire form the core’s diff / apply understand and the runtime mounts:

WaCard().child(WaSwitch()).to_node()

In a notebook you rarely call these directly — Widget does it for you; over a server they are served as the tree the browser mounts.