# Build a Counter App URL: /milo-cli/docs/applied-tutorials/build-a-counter/ Section: applied-tutorials Tags: tutorial, interactive, app, reducer, templates -------------------------------------------------------------------------------- This tutorial walks through a simple counter app: the smallest useful interactive Milo program. What You'll Learn 0/4 complete Write a pure reducer function Create a Kida terminal template Wire them together with App Run with hot reload via milo dev (function() { const checklist = document.currentScript.closest('.checklist'); if (!checklist) return; const progressBar = checklist.querySelector('.checklist-progress-bar'); const progressText = checklist.querySelector('.checklist-progress-text'); const checkboxes = checklist.querySelectorAll('input[type="checkbox"]'); if (!progressBar || !progressText || !checkboxes.length) return; function updateProgress() { const total = checkboxes.length; const checked = Array.from(checkboxes).filter(cb => cb.checked).length; const percentage = Math.round((checked / total) * 100); progressBar.style.width = percentage + '%'; progressText.textContent = checked + '/' + total; } checkboxes.forEach(function(checkbox) { checkbox.addEventListener('change', updateProgress); }); })(); Build the Counter 1Create a reducerA pure function that manages your state A reducer takes the current state and an action, then returns the next state. It does not mutate the old state. # 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 2Create a templateRender 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"]. 3Wire it togetherCreate 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 input, state, and rendering: it reads keyboard input, dispatches @@KEY actions to the reducer, re-renders the template on each state change, and returns the final state when the user quits. 4Run itStart your app with hot reload milo dev app:app --watch . Tip Tip The milo dev command uses the module:attribute convention. app:app means "import app.py and look up the app attribute." The --watch flag enables hot reload, so template edits show up immediately. 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] KeyReader captures raw terminal input and produces Key objects. Store dispatches @@KEY actions to your reducer. Reducer returns new state. Kida template renders state to terminal output. LiveRenderer diffs and redraws changed lines. This is Milo's Elm-style architecture: every state transition is explicit and testable. Next Steps Database State Management Store, middleware, combined reducers Arrow Clockwise Multi-Screen Flows Chain screens with the >> operator Interactive Forms Collect structured input with validation Terminal Input Handling Key objects, escape sequences, raw mode, and fallbacks -------------------------------------------------------------------------------- Metadata: - Author: lbliii - Word Count: 468 - Reading Time: 2 minutes