Tutorial — the Counter¶
Welcome! 👋 In this tutorial you build the counter, tempestweb's canonical
app, from scratch. It is a + button, a - button and a text showing the
count. Simple — but it exercises the entire wire contract: the view tree,
state, handlers, patches and the two execution modes.
We go one concept per page, in order:
-
How to write
view()and assemble typed widgets (Column,Row,Text,Button). -
set_state, the coalesced rebuild, and why handlers never touch the DOM. -
What the reconciler emits when the count changes — and how the client applies it.
-
The same
app.pyunder--mode wasmand--mode server, without changing a line.
Prerequisite
You only need to have done the Installation. Each page assumes only the previous one — start on page one and keep going.
What we will build¶
By the end, this is the complete app — exactly what lives in
examples/counter/app.py:
"""Counter — the canonical tempestweb example."""
from __future__ import annotations
from dataclasses import dataclass
from tempest_core import App, Button, Column, Row, Style, Text, Widget
from tempest_core.style import Edge
@dataclass
class CounterState:
"""State for the counter app."""
value: int = 0
def make_state() -> CounterState:
"""Build the initial state."""
return CounterState()
def view(app: App[CounterState]) -> Widget:
"""Render the counter UI from the current state."""
def increment() -> None:
app.set_state(lambda s: setattr(s, "value", s.value + 1))
def decrement() -> None:
app.set_state(lambda s: setattr(s, "value", s.value - 1))
return Column(
style=Style(gap=8.0, padding=Edge.all(16)),
children=[
Text(content=f"Count: {app.state.value}", key="label"),
Row(
style=Style(gap=4.0),
children=[
Button(label="-", on_click=decrement, key="dec"),
Button(label="+", on_click=increment, key="inc"),
],
),
],
)
Everything you need to know is here
There is no hidden magic. The next four pages explain every line above, piece by piece. Let's start with the view tree. 🚀