Tutorial: build your first interactive UI¶
In this tutorial you will build a small settings panel — a card with a switch and a reveal button — and make it interactive, all from Python. By the end you will have rendered web components, run behavior in the browser with no server, and wired a control two-way to state your Python code can read.
We will work in a Jupyter notebook, because it renders a spaday UI with no setup. Everything you learn here applies unchanged to a web app (see the transports guide afterwards).
Setup¶
Install spaday with the notebook host, and start a notebook:
pip install "spaday[widget]"
jupyter lab
Step 1 — render a component¶
In a cell, build a card containing a switch and display it:
from spaday import Widget
from spaday.components.webawesome import WaCard, WaSwitch
panel = WaCard().child(WaSwitch().text("Lamp"))
Widget(panel)
Run the cell. You should see a bordered card with a labelled toggle switch. WaCard and WaSwitch are
typed Python classes for WebAwesome components; .child(...) nests one inside
another, and Widget(...) renders the tree in the output area.
Step 2 — add a layout¶
Components nest, so add a second row and a heading using the same pattern. Use a Stack to lay the
children out vertically:
from spaday import element, Widget
from spaday.components.shell import Stack
from spaday.components.webawesome import WaCard, WaSwitch
panel = WaCard().child(
Stack()
.child(element("strong").text("Settings"))
.child(WaSwitch().text("Lamp"))
.child(WaSwitch().text("Notifications"))
)
Widget(panel)
Run it. You should see a card titled Settings with two switches stacked under it. Stack is one of
spaday’s spa-* layout components; element("strong") is an escape hatch for a plain HTML tag.
Step 3 — make it do something, in the browser¶
Now add a button that reveals a panel — and have it run in the browser, with no call back to
Python. Attach a declarative action with .on(...):
from spaday import by_id, element, Toggle, Widget
from spaday.components.shell import Stack
from spaday.components.webawesome import WaButton, WaCallout, WaCard, WaSwitch
panel = WaCard().child(
Stack()
.child(element("strong").text("Settings"))
.child(WaSwitch().text("Lamp"))
.child(WaButton(variant="brand").text("Details").on("click", Toggle(by_id("info"), "hidden")))
.child(WaCallout().prop("id", "info").prop("hidden", True).text("Runs entirely client-side."))
)
Widget(panel)
Run it and click Details. The callout appears and disappears each click. Toggle(by_id("info"), "hidden") is an action — serializable data, not Python code — that spaday’s runtime interprets in the
browser. Your Python kernel is never contacted when you click.
Step 4 — bind a control to state¶
Client-side behavior is good; reactive state is better. Give the widget a state model and bind the
switch’s checked to a field of it, two-way:
from spaday import element, Widget
from spaday.components.shell import Stack
from spaday.components.webawesome import WaCard, WaSwitch
panel = WaCard().child(
Stack()
.child(element("strong").text("Settings"))
.child(WaSwitch().text("Lamp").bind("checked", "lamp", mode="two-way"))
)
w = Widget(panel, state={"lamp": True})
w
Run it. The switch starts on, because the lamp field is True. Now read the state back in Python —
in a new cell:
w.state
Flip the switch in the rendered widget, then re-run w.state. You should see {'lamp': False}. The
control wrote the field. It works the other way too — set the field from Python:
w.state = {"lamp": True}
The switch turns back on. The binding keeps the control and the state field in sync in both directions.
Step 5 — react to changes in Python¶
Register a callback to run whenever the state changes (including from a click in the browser):
w.on_state(lambda state: print("settings:", state))
Now flip the switch in the widget. Your cell prints settings: {'lamp': False}. You have a UI whose
interactions run in the browser and report back to Python.
What you built¶
A web-component UI authored entirely in typed Python.
Behavior (
Toggle) that runs client-side with no round-trip.A control two-way-bound to state, readable and writable from Python.
Next steps¶
How spaday works — why behavior is data and how one Rust core drives both Python and the browser.
Add behavior and reactivity — the full action DSL and binding kinds (including computed props derived from state).
Author a component tree — props, slots, keys, and the shell components in depth.
Sync a UI to a server over transports — the same panel, multi-tenant, on a webserver.