This guide walks you through building a simple counter app — the "hello world" of Milo.
What You'll Learn
- Write a pure reducer function
- Create a Kida terminal template
- Wire them together with
App - Run with hot reload via
milo dev
Build the Counter
- 1
Create a reducer
A pure function that manages your state
A reducer takes the current state and an action, and returns the next state. It never mutates — always return a new value.
# app.py from milo import App, Action def reducer(state, action): if state is None: return {"count": 0} if action.type == "@@KEY" and action.payload.char == " ": return {**state, "count": state["count"] + 1} if action.type == "@@KEY" and action.payload.char == "r": return {**state, "count": 0} return stateThe reducer handles three cases:
Action Condition Result @@INITstate is NoneReturn default state {"count": 0}@@KEYchar == " "Increment counter @@KEYchar == "r"Reset counter to 0 - 2
Create a template
Render state to the terminal with Kida
Milo uses Kida templates for rendering. Create a template file:
{# counter.kida #} Count: {{ count }} [SPACE] Increment [R] Reset [Ctrl+C] QuitYour state dict becomes the template context —
{{ count }}renders the current value ofstate["count"]. - 3
Wire it together
Create an App and run the event loop
app = App(template="counter.kida", reducer=reducer, initial_state=None) final_state = app.run() print(f"Final count: {final_state['count']}")Appconnects the pieces: it reads keyboard input, dispatches@@KEYactions to the reducer, re-renders the template on every state change, and returns the final state when the user quits. - 4
Run it
Start your app with hot reload
milo dev app:app --watch .
Tip
Themilo dev command uses the module:attribute convention. app:app means "import app from app.py and look up the app attribute." The --watch flag enables hot reload — edit counter.kidaand see changes instantly.
What just happened?
- KeyReader captures raw terminal input and produces
Keyobjects - Store dispatches
@@KEYactions to your reducer - Reducer returns new state (immutable — no mutation)
- Kida template renders state to terminal output
- LiveRenderer diffs and redraws only changed lines
This is the Elm Architecture — a unidirectional data flow where every state transition is explicit and testable.