Quickstart

Build your first Milo app in 5 minutes.

3 min read 508 words

This guide walks you through building a simple counter app — the "hello world" of Milo.

What You'll Learn

0/4 complete
  • Write a pure reducer function
  • Create a Kida terminal template
  • Wire them together withApp
  • Run with hot reload viamilo dev

Build the Counter

  1. 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 state
    

    The reducer handles three cases:

    Action Condition Result
    @@INIT state is None Return default state{"count": 0}
    @@KEY char == " " Increment counter
    @@KEY char == "r" Reset counter to 0
  2. 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] Quit
    

    Your state dict becomes the template context — {{ count }} renders the current value of state["count"].

  3. 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']}")
    

    App connects 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. 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?

flowchart LR K[Keyboard] -->|"@@KEY"| R[Reducer] R -->|new state| S[Store] S -->|state dict| T[Kida Template] T -->|ANSI output| Term[Terminal]
  1. KeyReader captures raw terminal input and producesKeyobjects
  2. Store dispatches@@KEYactions to your reducer
  3. Reducer returns new state (immutable — no mutation)
  4. Kida template renders state to terminal output
  5. LiveRenderer diffs and redraws only changed lines

This is the Elm Architecture — a unidirectional data flow where every state transition is explicit and testable.

Next steps