Milo renders terminal UI through Kida templates — the same syntax you'd use for HTML, adapted for terminal output with ANSI colors and live rendering.
Template loader chain
Milo's template environment searches three locations in order:
from milo.templates import get_env
env = get_env() # Pre-configured environment with all loaders
template = env.get_template("my_screen.kida")
Built-in templates
| Template | Description |
|---|---|
form.kida |
Full form layout — iterates field specs and renders each field |
field_text.kida |
Text/password input field with cursor |
field_select.kida |
Select field with[x] / [ ]radio indicators |
field_confirm.kida |
Yes/No confirm field |
help.kida |
argparse help output styled with Kida |
progress.kida |
Unicode progress bar (█ / ░) |
Tip
Override any built-in template by placing a file with the same name in your template directory. The loader chain checks your templates first.
Template auto-discovery
App.from_dir() finds a templates/directory relative to the calling file and sets up the loader chain automatically:
from milo import App
app = App.from_dir(
__file__,
template="dashboard.kida",
reducer=reducer,
initial_state=State(),
)
app.run()
This eliminates manual FileSystemLoader and get_env() setup. Pass templates_dir="views"to use a different directory name.
Built-in component macros
Milo ships reusable template macros incomponents/_defs.kida. Import them into any template:
{% from "components/_defs.kida" import header, section, key_hints, selectable_list %}
| Macro | Description |
|---|---|
header(name, version, description) |
App banner with name, version, and description |
header_box(name, version, description) |
Boxed header with rounded border |
section(title, color) |
Colored section heading |
status_line(level, message, detail) |
Icon + message line (success, error, warning, info) |
kv_pair(label, value, width) |
Key-value pair with dot-fill alignment |
kv_list(items, width) |
List of key-value pairs |
def_list(items) |
Definition list (term + indented description) |
example_block(examples) |
Description + command examples |
tag_list(tags) |
Colored inline labels |
breadcrumb(parts, sep) |
Hierarchical path display |
command_row(name, description, aliases, tags) |
Command with aliases and tags |
key_hints(hints) |
Keyboard shortcut bar |
selectable_list(items, cursor, render, empty) |
Cursor-highlighted item list |
scrollable_list(items, cursor, render, height, scroll_offset, empty) |
Viewport slice with overflow indicators |
format_time(seconds) |
Seconds toMM:SS.CCformat |
Selectable list
Renders a cursor-navigable list. Pass a render macro that takes(item, is_selected):
{% from "components/_defs.kida" import selectable_list %}
{% def render_todo(todo, is_selected) %}
{% if todo.done %}{{ icons.check | green }} {{ todo.text | dim }}
{% else %}{{ icons.cross | red }} {{ todo.text }}{% endif %}
{% enddef %}
{{ selectable_list(visible, state.cursor, render_todo) }}
Scrollable list
Likeselectable_listbut clips to a viewport height and shows "N more above/below" indicators:
{% from "components/_defs.kida" import scrollable_list %}
{{ scrollable_list(state.entries, state.cursor, render_entry,
height=15, scroll_offset=state.scroll_offset) }}
Format time
Converts seconds (float) toMM:SS.CC:
{% from "components/_defs.kida" import format_time %}
{{ format_time(state.elapsed) | bold | green }}
Writing templates
Templates use Kida syntax (similar to Jinja2). Your state dict becomes the template context:
{# dashboard.kida #}
{{ "Status Dashboard" | bold }}
{{ "=" * 40 | fg("dim") }}
{% for service in services %}
{{ service.name | ljust(20) }}{{ service.status | badge }}
{% end %}
Uptime: {{ uptime | duration }}
Common terminal filters
| Filter | Effect |
|---|---|
bold |
Bold text |
dim |
Dimmed text |
fg("color") |
Foreground color (red, green, blue, cyan, etc.) |
bg("color") |
Background color |
ljust(n) |
Left-justify to n characters |
rjust(n) |
Right-justify to n characters |
center(n) |
Center to n characters |
badge |
Status badge (colored based on value) |
cell_width |
Measure terminal display-cell width, ignoring ANSI escapes |
cell_fit(n) |
Truncate and pad to exactly n display cells |
cell_truncate(n) |
Truncate to n display cells |
cell_pad(n) |
Pad on the right to n display cells |
cell_rpad(n) |
Pad on the left to n display cells |
See the Kida terminal reference for the full filter list.
Use thecell_*filters for fixed-width boxes, rails, tables, and diagnostic
cards that include ANSI color or wide Unicode glyphs. Plainljust(n)and
rjust(n)operate on Python string length, which can misalign terminal output
when glyph display width differs from code-point count.
Live rendering
When running inside anApp, Milo uses Kida's LiveRendererfor flicker-free terminal updates. The renderer diffs the previous and next output and only redraws changed lines.
IfLiveRenderer is unavailable, Milo falls back to ANSI clear-screen writes (\033[2J\033[H).
Static HTML rendering
For one-shot output (documentation, exports), userender_html:
from milo import render_html
html = render_html(state={"count": 42}, template="counter.kida")
Note
render_htmlrenders the template once and returns the result as a string. It does not start an event loop or read keyboard input.