Widgets¶
A widget in maya-py is a function that returns an Element — the same
immutable node a card(...) or row(...) produces. Each one wraps a native
maya C++ renderer (see the pipeline in concepts.md §2), so the
work happens once at the pybind11 boundary and the result drops straight into
any layout:
from maya_py import col, sparkline, gauge, table, show
show(col(
sparkline([3, 1, 4, 1, 5, 9, 2, 6], label="req/s", show_last=True),
gauge(0.72, "load"),
table(["Name", "Score"], [["Ada", "99"], ["Bob", "7"]], bordered=True),
))
Two things hold across the whole catalog:
- Widgets are values, not commands. They return a description of pixels; you compose them, you don't "draw" them. Re-read concepts.md §1.
- Colours are friendly everywhere. Any
color=/fill=/track=argument accepts a name ("sky","red"), an(r, g, b)tuple, a"#rrggbb"string, or a mayaColor.Nonemeans "use the widget default."
Every example below is runnable as-is — wrap it in show(...) (or to_string)
to see it, as in the snippet above. Full gallery:
examples/widgets_gallery.py
and examples/dashboard.py.
The count: 77 native renderers¶
The catalog covers 77 native renderers, every one a thin wrapper over a
maya C++ widget. Counted from widgets.py's __all__:
| Category | Renderers | n |
|---|---|---|
| charts & meters | sparkline, gauge, progress, bar_chart, line_chart, heatmap, flame_chart, waterfall, activity_indicator | 9 |
| controls | checkbox, toggle, radio, select, slider, button | 6 |
| text & labels | badge, divider, spinner, callout, status_banner, system_banner, breadcrumb, tabs, gradient, link, title_chip, model_badge, file_ref, markdown, html, phase_chip, phase_accent | 17 |
| structure & nav | table, tree, list_view, menu, disclosure, key_help, calendar, timeline, picker, popup, overlay, modal, command_palette, log_viewer | 14 |
| agent UI | thinking, todo_list, toast, inline_diff, tool_call, plan_view, context_window, context_gauge, diff_view, git_graph, git_status, user_message, assistant_message, shortcut_row, activity_bar, file_changes, api_usage, cost_tracker, checkpoint_divider, turn_divider, streaming_cursor, token_stream, token_stream_sparkline, search_result, changes_strip, welcome_screen | 26 |
| graphics | image, canvas, cell_grid | 3 |
| scrolling | viewport, scrollbar | 2 |
| color helpers | gradient (listed under text) | — |
| Total | 77 |
(The category buckets are presentational; the __all__ total is what binds.)
What is not counted as a renderer: the Canvas/Surface classes are
imperative drawing surfaces, not presentational renderers (they ultimately
emit the same half-block / braille paint as canvas(...)); scroll_state() /
scroll_handle(...) are state helpers that produce/route a ScrollState;
and rgb_lerp(...) / ramp(...) are colour helpers (they return tuples /
int LUTs, not an Element). The widget enums (GaugeStyle, ColumnAlign,
ButtonVariant, TaskStatus, ToastLevel, TodoItemStatus,
TodoListStatus, PopupStyle, BannerLevel, ToolCallStatus,
ToolCallKind, FileChangeKind, TurnRole, CursorStyle, SearchKind,
SearchStatus, ScrollbarStyle) are option types.
Layout primitives (card, row, col, tcol/trow, …) live in
layout.md, not here.
Input-shape flexibility¶
Several widgets accept loose inputs and normalise them for you. Worth internalising before the reference:
- List items (
list_view,menu) accept a barestr, a(label, description, icon)tuple, or adict. - Timeline & todo statuses accept either a string
(
"pending","in_progress","completed","done","running","failed") or the exported enum (TaskStatus,TodoItemStatus,TodoListStatus). Strings are lower-cased and spaces become underscores, so"In Progress"works. - Chart series (
bar_chart,flame_chart,waterfall) take tuples where trailing fields (colour, depth, …) are optional. - Cells in
tableare stringified for you — pass numbers directly.
Charts & meters¶
sparkline¶
sparkline(data: Sequence[float], *, label: str = "", color=None,
show_min_max: bool = False, show_last: bool = False) -> Element
An inline mini bar chart from a sequence of numbers.
from maya_py import sparkline, show
show(sparkline([3, 1, 4, 1, 5, 9, 2, 6, 5, 3], label="req/s",
color="sky", show_last=True))
Notable: show_min_max appends the range; show_last appends the most recent
value. Both are off by default for a clean one-line glyph.
gauge¶
A meter over 0..1. style is "arc" (default) or "bar", or a GaugeStyle
member.
progress¶
progress(value: float, label: str = "", *, width: int = 0, fill=None,
track=None, show_track: bool = True,
show_percentage: bool = True) -> Element
A horizontal progress bar over 0..1. width=0 fills the available space.
Notable: set show_track=False for a bar with no background groove, or
show_percentage=False to drop the trailing %.
bar_chart¶
Horizontal bars. Each bars entry is (label, value) or
(label, value, color). max_value=0 auto-scales to the largest bar; color
is the default for bars without their own.
from maya_py import bar_chart, show
show(bar_chart([("Mon", 30), ("Tue", 80, "lime"), ("Wed", 55)], color="sky"))
line_chart¶
A braille line chart with a y-axis. height is the plot height in cells.
from maya_py import line_chart, show
show(line_chart([1, 3, 2, 5, 4, 7, 6, 9, 8], height=6, label="latency"))
heatmap¶
heatmap(grid: Sequence[Sequence[float]], *, low=None, high=None,
x_labels: Sequence[str] = (), y_labels: Sequence[str] = ()) -> Element
A 2-D heatmap. grid is rows of floats; each cell colour-interpolates from
low to high. Optional axis labels.
from maya_py import heatmap, show
show(heatmap([[0, 2, 4], [1, 3, 5], [6, 4, 2]],
low="#1e3a8a", high="orange", # color: name / "#rrggbb" / (r,g,b)
x_labels=["a", "b", "c"], y_labels=["x", "y", "z"]))
flame_chart¶
flame_chart(spans: Sequence[Any], *, time_scale: float = 0.0, width: int = 60,
show_times: bool = True) -> Element
A flamegraph. Each span is (label, start, duration, depth, color) — depth
and color are optional trailing fields. time_scale=0 auto-scales; width
is the chart width in columns.
from maya_py import flame_chart, show
show(flame_chart([
("main", 0.0, 10.0, 0),
("parse", 1.0, 4.0, 1, "sky"),
("eval", 5.0, 4.0, 1, "lime"),
]))
waterfall¶
waterfall(entries: Sequence[Any], *, time_scale: float = 0.0, bar_width: int = 30,
show_labels: bool = True, frame: int = 0) -> Element
A request waterfall (think browser network tab). Each entry is
(label, start, duration, color) — color optional. frame advances any
in-flight animation; bar_width is the track width in columns.
from maya_py import waterfall, show
show(waterfall([
("DNS", 0.0, 20.0, "slate"),
("connect", 20.0, 40.0, "sky"),
("download", 60.0, 120.0, "lime"),
]))
Controls¶
These render an interactive control in a given state — maya draws the visual, you own the state (toggle the bool, move the cursor) in your update logic. None of them capture keys on their own; see apps.md for wiring them up.
checkbox¶
A [x] label box in its checked/unchecked state.
from maya_py import checkbox, col, show
show(col(checkbox("Accept terms", True), checkbox("Subscribe", False)))
toggle¶
An on/off switch (●━━ / ━━◯).
radio¶
A radio group; selected is the chosen index. visible_count>0 windows a long
list to that many rows.
select¶
select(items: Sequence[str], *, cursor: int = 0, indicator: str = "",
visible_count: int = 0) -> Element
A single-choice list with a ❯ cursor on row cursor. indicator overrides
the cursor glyph; visible_count>0 windows the list.
slider¶
slider(value: float, label: str = "", *, min: float = 0.0, max: float = 1.0,
step: float = 0.01, width: int = 24, fill=None, track=None) -> Element
A horizontal slider filled to value within [min, max]. width is the track
width in columns — a fixed width is required for a standalone render.
from maya_py import slider, show
show(slider(60, "volume", min=0, max=100, step=5, width=30, fill="sky"))
button¶
A button. variant is "default" / "primary" / "danger" / "ghost", or a
ButtonVariant member.
from maya_py import button, row, show
show(row(button("Save", variant="primary"), button("Delete", variant="danger")))
Text & labels¶
badge¶
A bracketed tag. kind colours it: "" (neutral), "success", "error",
"warning", "info", "tool". style is an explicit maya Style override.
from maya_py import badge, row, show
show(row(badge("NEW", kind="success"), badge("DEPRECATED", kind="warning")))
divider¶
A horizontal rule with an optional centered label. line is a BorderStyle
member or its name ("single", "double", "dashed", …; defaults to single).
spinner¶
A single animated spinner frame. It does not animate itself — advance it per
frame from a continuous loop (@app.on_frame, animate, or fps>0); see
examples/live_spinner.py.
callout¶
A severity box with a title and body. kind: "info" / "success" /
"warning" / "error".
status_banner¶
A one-line full-width status strip. kind: "info" / "warning" / "error".
breadcrumb¶
A home › projects › file path trail.
from maya_py import breadcrumb, show
show(breadcrumb(["home", "projects", "maya-py", "widgets.md"]))
tabs¶
A tab bar with one active tab highlighted.
gradient¶
Text with a per-character colour gradient. Pass start+end for a two-stop
ramp, or a list of 2+ colours as stops for an evenly-distributed multi-stop
ramp (it takes precedence over start/end). bold=True bolds every
character. All colour arguments accept any colour form (name / (r,g,b) /
"#rrggbb" / Color).
from maya_py import gradient, show
show(gradient("maya-py", "magenta", "cyan"))
show(gradient("R A I N B O W", stops=["red", "orange", "lime", "cyan", "violet"]))
show(gradient("HEADLINE", "#ff5f87", "#5fafff", bold=True))
Note (concepts.md §7): a static gradient is fine, but an animated one that emits a fresh shade per cell per frame grows maya's style pool — quantize to a fixed palette in hot loops.
link¶
An OSC-8 hyperlink (clickable in supporting terminals). show_icon prepends a
link glyph.
from maya_py import link, show
show(link("maya-py on GitHub", "https://github.com/1ay1/maya-py", show_icon=True))
title_chip¶
A rounded title chip (the agent-header style). max_chars>0 truncates.
model_badge¶
A model-name badge (e.g. ✦ Opus 4). compact shortens it.
file_ref¶
A 📄 path:line file reference. line=0 omits the line number; show_icon
toggles the leading glyph.
markdown¶
Renders GFM markdown (headings, lists, tables, code, emphasis) to an Element — the same renderer maya uses for agent output.
from maya_py import markdown, show
show(markdown("# Title\n\n- **bold** item\n- `code` item\n\n> a quote"))
html¶
Renders a subset of HTML to an Element — the same engine maya's markdown widget
uses for inline tags. theme is dark / light / dark_ansi / light_ansi.
error_block¶
error_block(error_type: str, message: str = "", *, detail: str = "", hint: str = "",
severity: str = "error", trace: Sequence[str] = ()) -> Element
A structured error card: a typed header, a message, optional detail / hint
lines, and a trace stack. severity is error / warning / info.
from maya_py import error_block, show
show(error_block("TypeError", "cannot add int and str",
hint="cast with str() or int()",
trace=["file.py:12 in add()", "main.py:3 in <module>"]))
activity_indicator¶
The animated "working…" ticker (a rotating word pool + sweep). Pass an optional
trailing token like an elapsed time as detail.
Structure & navigation¶
table¶
table(columns: Sequence[Any], rows: Sequence[Sequence[Any]], *, stripe: bool = True,
bordered: bool = False, title: str = "", cell_padding: int = 1) -> Element
A data table. columns is a list of header strings, or (header, width, align)
tuples where align is "left" / "center" / "right" (or a ColumnAlign
member; width=0 auto-sizes). rows is a list of row-lists; cells are
stringified for you.
from maya_py import table, show
show(table(
[("Name", 0, "left"), ("Score", 8, "right")],
[["Ada", 99], ["Bob", 7], ["Cy", 42]],
bordered=True, stripe=True, title="Leaderboard",
))
Notable: stripe zebra-stripes rows; bordered draws box borders;
cell_padding is the horizontal padding inside each cell. For per-frame hot
tables, pass tuple cells into layout instead (see concepts.md §7).
tree¶
A collapsible tree. root is a nested dict
{label, expanded, selected, children: [...]}.
from maya_py import tree, show
show(tree({
"label": "src", "expanded": True, "children": [
{"label": "maya_py", "expanded": True, "children": [
{"label": "widgets.py", "selected": True},
{"label": "easy.py"},
]},
{"label": "tests"},
],
}))
list_view¶
list_view(items: Sequence[Any], *, cursor: int = 0, filterable: bool = False,
visible_count: int = 0) -> Element
A scrollable item list with a highlighted cursor row. Items are strings,
(label, description, icon) tuples, or dicts. filterable shows a filter
affordance; visible_count>0 windows the list.
from maya_py import list_view, show
show(list_view([
"Plain item",
("Deploy", "push to prod", "🚀"),
{"label": "Settings", "description": "preferences", "icon": "⚙"},
], cursor=1))
menu¶
A dropdown menu. Items are strings, dicts, or
(label, shortcut, enabled, separator) tuples.
from maya_py import menu, show
show(menu([
("Open", "⌘O", True, False),
("Save", "⌘S", True, False),
("", "", False, True), # separator row
("Quit", "⌘Q", True, False),
], cursor=0))
disclosure¶
A collapsible section. When open and content is given, the content renders
beneath the header.
from maya_py import disclosure, col, show
show(disclosure("Details", open=True, content=col("line one", "line two")))
key_help¶
A keyboard-shortcut cheat sheet. Each binding is (key, description) or
(key, description, group).
from maya_py import key_help, show
show(key_help([
("q", "quit", "general"),
("↑/↓", "move", "nav"),
("enter", "select", "nav"),
], title="Shortcuts"))
calendar¶
A month grid for year/month (1-12). today is an optional (y, m, d) tuple
to highlight the current day.
timeline¶
timeline(events: Sequence[Any], *, show_connector: bool = True, compact: bool = False,
frame: int = 0, track_width: int = 40) -> Element
A vertical event timeline. Each event is a dict
{label, detail, duration, status, bar_width} or a tuple
(label, detail, duration, status, bar_width). status accepts a string
("pending" / "in_progress" / "completed") or a TaskStatus member.
frame advances any in-flight bar animation.
from maya_py import timeline, TaskStatus, show
show(timeline([
{"label": "Fetch", "detail": "GET /api", "duration": "0.4s", "status": "completed"},
{"label": "Parse", "duration": "0.1s", "status": TaskStatus.InProgress},
{"label": "Render", "status": "pending"},
]))
picker¶
picker(rows: Sequence[Any] = (), *, title: str = "", accent=None, selected: int | None = None,
header: Sequence[Element] = (), footer: Sequence[Element] = (),
items: Sequence[Element] = (), min_width: int = 50, viewport_h: int = 14,
cursor_color=None, active_color=None) -> Element
A bordered command-palette / fuzzy-picker panel (Zed / VS Code style). Each
rows entry is a string, a (leading, trailing, selected, active) tuple, or a
dict with those keys (plus optional leading_style / trailing_style). The
selected row gets the cursor edge-bar + bold; an active row gets the magenta
"current" bar. selected is the 0-based cursor index (auto-derived from the
first row flagged selected if omitted).
header / footer are pre-built Elements painted above/below the list (a
search line, a ↑↓ move hint). For full layout control, pass raw items
Elements instead of rows (no auto-styling).
from maya_py import picker, show
show(picker(
rows=[
("Go to File", "⌘P"),
("Go to Symbol", "⌘O"),
("Command Palette", "⌘⇧P"),
],
title="Quick Open", selected=0, accent="magenta",
))
modal¶
A centered dialog: a title bar, a body, and a footer of action buttons.
content is text or an Element; buttons is a list of labels or
(label, variant) tuples where variant is default / primary / danger;
focused highlights that button index. Pair it with overlay(base, modal(...))
to float it over your UI.
from maya_py import modal, show
show(modal("Delete file?", content="This cannot be undone.",
buttons=[("Cancel", "default"), ("Delete", "danger")], focused=1))
command_palette¶
A fuzzy command menu (the Ctrl-K palette). Each command is a name, a
(name, description, shortcut) tuple, or a dict with those keys. cursor is
the highlighted row.
from maya_py import command_palette, show
show(command_palette([("Open File", "", "^O"),
("Save", "write the buffer", "^S")], cursor=0))
log_viewer¶
A scrolling log panel. Each entry is a (timestamp, message, level) tuple or a
dict {timestamp, message, level}; level is debug / info / warn /
error. visible>0 windows the rows; scroll is the top offset.
from maya_py import log_viewer, show
show(log_viewer([("12:00:01", "Started", "info"),
("12:00:02", "Disk full", "error")], visible=10))
Agent UI¶
These match the visual language of a coding agent — thinking traces, task lists, tool calls, message bubbles, git status, context budgets. See examples/agent.py and examples/widgets_gallery.py.
thinking¶
thinking(content: str = "", *, active: bool = False, expanded: bool = True,
max_lines: int = 0) -> Element
A collapsible "thinking" block (an agent reasoning trace). active shows the
live in-progress treatment; expanded shows the body; max_lines>0 caps the
visible lines.
from maya_py import thinking, show
show(thinking("Let me check the config, then run the tests…",
active=True, max_lines=3))
todo_list¶
todo_list(items: Sequence[Any], *, description: str = "", status="pending",
elapsed: float = 0.0, expanded: bool = True) -> Element
An agent-style todo card. Items are strings or (content, status) tuples,
where the item status is "pending" / "in_progress" / "completed" (or a
TodoItemStatus member). The card status is "pending" / "running" /
"done" / "failed" (or a TodoListStatus member). elapsed shows a timer.
from maya_py import todo_list, TodoItemStatus, show
show(todo_list([
("Read the source", "completed"),
("Write the docs", TodoItemStatus.InProgress),
("Verify the count", "pending"),
], description="Documenting widgets", status="running", elapsed=12.5))
toast¶
A stack of toast notifications. Each is a string or (message, level) tuple;
level is "info" / "success" / "warning" / "error" (or a ToastLevel
member).
from maya_py import toast, ToastLevel, show
show(toast([
("Saved", "success"),
("Low memory", ToastLevel.Warning),
"Plain info toast",
]))
inline_diff¶
A word-level inline diff between before and after. label titles the block;
show_header toggles the header row.
from maya_py import inline_diff, show
show(inline_diff("the quick brown fox",
"the slow brown dog",
label="edit"))
tool_call¶
tool_call(name: str, *, kind="other", description: str = "", status="pending",
elapsed: float = 0.0, expanded: bool = False, content=None) -> Element
An agent tool-call card. kind is read / edit / execute / search /
delete / move / fetch / think / agent / other (or a ToolCallKind);
status is pending / running / completed / failed / confirmation (or
a ToolCallStatus). content is an optional Element shown when expanded.
from maya_py import tool_call, show
show(tool_call("Read", kind="read", description="src/auth.py",
status="completed", elapsed=1.2))
plan_view¶
A task checklist. Each task is a string (pending) or a (label, status) tuple;
status is pending / in_progress / completed (or a TaskStatus).
from maya_py import plan_view, show
show(plan_view([("Audit widgets", "completed"),
("Write docs", "in_progress"),
"Ship it"]))
phase_chip¶
phase_chip(verb: str, *, glyph: str = "", color=None, breathing: bool = False,
frame: int = 0, verb_width: int = 10, elapsed: float = -1.0) -> Element
A status chip for the current agent phase ("Thinking", "Running", …). breathing
+ frame animate a pulse; elapsed >= 0 appends a timer.
context_gauge¶
A compact token-budget meter (used/max) with a threshold-coloured bar.
context_window¶
context_window(segments: Sequence[Any], *, max_tokens: int = 200000, width: int = 0,
show_labels: bool = True, show_percent: bool = True) -> Element
A segmented context-budget bar. Each segment is a (label, tokens) /
(label, tokens, color) tuple or a dict; segments fill toward max_tokens.
diff_view¶
diff_view(path: str, diff: str, *, show_border: bool = True,
show_line_numbers: bool = True) -> Element
Renders a unified-diff string (@@ … @@ hunks, +/- lines) with syntax
colouring under a path header.
git_graph¶
git_graph(commits: Sequence[Any], *, max_branches: int = 0, show_hash: bool = True,
show_author: bool = False, show_time: bool = True) -> Element
An ASCII commit graph. Each commit is a dict
{hash, message, author, time, branch, is_merge, is_head} or the matching tuple.
git_status¶
git_status(*, branch: str = "", ahead: int = 0, behind: int = 0, modified: int = 0,
staged: int = 0, untracked: int = 0, deleted: int = 0, conflicts: int = 0,
compact: bool = True, changed_files: Sequence[str] = ()) -> Element
A git-status summary (branch, ahead/behind, dirty counts). compact renders a
single line; otherwise an expanded list including changed_files.
activity_bar¶
activity_bar(*, model: str = "", input_tokens: int = 0, output_tokens: int = 0,
cost: float = 0.0, context_pct: int = 0, status: str = "") -> Element
A single-line model/usage status strip (the Claude Code / Zed activity bar):
model · ↑in ↓out · $cost · ctx%. Omitted fields drop out.
from maya_py import activity_bar, show
show(activity_bar(model="claude-opus-4", input_tokens=12000, output_tokens=3400,
cost=0.21, context_pct=42, status="streaming"))
file_changes¶
A session file-change summary with +/~/- status icons and ±line counts.
Each change is a dict {path, kind, added, removed} or a tuple
(path, kind, added, removed); kind is created / modified / deleted /
renamed (or a FileChangeKind).
from maya_py import file_changes, show
show(file_changes([
("src/auth.py", "modified", 18, 4),
("src/new.py", "created", 60, 0),
("old.py", "deleted", 0, 120),
]))
api_usage¶
api_usage(*, requests: int = 0, request_limit: int = 0, tokens: int = 0,
token_limit: int = 0, latency_ms: int = 0, errors: int = 0,
compact: bool = False) -> Element
An API rate-limit / usage panel: request + token mini-bars that shade
green → yellow → red as they approach their limits, plus latency and error
count. A limit of 0 hides that row.
cost_tracker¶
A per-turn + cumulative token/cost breakdown. Each turn is a dict with keys
input / output / cache_read / cache_write / cost / latency_ms, or a
tuple (input, output, cost).
token_stream¶
token_stream(*, total_tokens: int = 0, tokens_per_sec: float = 0.0,
peak_rate: float = 0.0, elapsed: float = 0.0,
history: Sequence[float] = (), color=None, compact: bool = False) -> Element
A live token-generation visualiser: a sparkline of history plus
rate / total / peak / elapsed stats. compact collapses it to one line.
token_stream_sparkline¶
token_stream_sparkline(*, rate: float = 0.0, total: int = 0,
history: Sequence[float] = (), color=None,
live: bool = False, adaptive: bool = False,
max_spark_cells: int = 64) -> Element
A ⚡ 23.4 t/s ▁▂▃▄▅▆▇█ streaming status slot. Every segment is a stable
display width, so neighbouring chips don't shift as numbers tick.
live=False dims it (frozen).
adaptive=True makes the slot grow to fill the free space between the left
group and the right chips (a grow(1) component): the sparkline sizes itself to
the allocated width, clamped to [8, max_spark_cells] and right-pinned so the
newest sample stays glued to the chips on its right. adaptive=False (default)
is the fixed 16-cell chip.
from maya_py import token_stream_sparkline, show
hist = [3, 5, 8, 6, 9, 12, 10, 14, 11]
show(token_stream_sparkline(rate=23.4, history=hist, live=True, adaptive=True))
streaming_cursor¶
An animated typing/streaming indicator. style is block / dots / bar /
pulse (or a CursorStyle); frame advances the animation; active=False
freezes it.
checkpoint_divider¶
A full-width ↺ Restore checkpoint rule marking a snapshot point.
turn_divider¶
A conversation-turn separator (─── ✦ Claude #3 ───). role is user /
assistant / system / tool (or a TurnRole); turn_number numbers it.
phase_accent¶
A soft-shelf full-width rule (a row of ▁/▔ half-blocks) in a phase colour.
position is top or bottom — pair it above/below a phase block.
search_result¶
search_result(groups: Sequence[Any], *, kind=None, pattern: str = "",
status=None, elapsed: float = 0.0, expanded: bool = True,
max_matches_per_file: int = 0) -> Element
A Grep/Glob search-results panel (bordered file groups + matches). groups is
a list of dicts {file_path, matches} or tuples (file_path, matches); each
match is (line, content), a string, or {line, content}. kind is grep /
glob (or a SearchKind); status is pending / searching / done /
failed (or a SearchStatus).
from maya_py import search_result, show
show(search_result([
("src/auth.py", [(12, "def login(user):"), (88, " return token")]),
("src/api.py", [(4, "import auth")]),
], kind="grep", pattern="auth", status="done", elapsed=0.03))
changes_strip¶
changes_strip(changes: Sequence[Any], *, border_color=None, text_color=None,
accept_color=None, reject_color=None) -> Element
A bordered "session has pending changes" banner over a file list — the same
change shape as file_changes. Empty changes renders to an empty Element, so
you can drop it into a stack unconditionally.
welcome_screen¶
welcome_screen(*, tagline: str = "", model_badge: Element | None = None,
profile_label: str = "", profile_color=None,
starters_title: str = "", starters: Sequence[str] = (),
hint_intro: str = "", hints: Sequence[Any] = (),
sigil_color=None, accent_color=None,
sigil_draw_ms: int = 0, max_rows: int = 0) -> Element
An empty-thread brand splash: a pixel-art wordmark, tagline, a model +
profile chip row, an optional starters card, and a hint footer. model_badge
is a built Element (e.g. model_badge("Opus 4")); hints is a list of
(key, label) or (key, label, color) tuples. sigil_draw_ms=0 renders the
completed mark statically; raise it for the cascade-in intro inside an app loop.
from maya_py import welcome_screen, model_badge, show
show(welcome_screen(
tagline="your terminal coding agent",
model_badge=model_badge("Opus 4"),
profile_label="default",
starters_title="Try",
starters=["Explain this repo", "Fix the failing test"],
hints=[("q", "quit"), ("?", "help")],
))
user_message / assistant_message¶
Chat bubbles: user_message is a bordered user turn, assistant_message a
padded reply block. content is text or an Element.
system_banner¶
A full-width system notice rule. level is info / success / warning /
error (or a BannerLevel).
shortcut_row¶
A width-adaptive keybinding hint row. bindings is a list of (key, label) or
(key, label, key_color, priority) tuples; lower-priority bindings degrade or
drop first when space runs out.
from maya_py import shortcut_row, show
show(shortcut_row([("q", "quit"), ("/", "search"), ("?", "help")]))
popup / overlay¶
popup(content: str, *, style="info") -> Element
overlay(base, over, *, present: bool = True) -> Element
popup is a floating notice box (style: info / warning / error, or a
PopupStyle). overlay z-stacks over on top of base for a modal layer;
with present=False only base renders — the toggle you flip to show/hide a
modal.
Graphics¶
image¶
A 1-bit braille image. pixels is a 2-D grid of truthy/falsy values (on/off
dots), packed into braille cells (denser than the cell grid). color tints the
dots.
from maya_py import image, show
grid = [[1 if (x + y) % 2 == 0 else 0 for x in range(16)] for y in range(8)]
show(image(grid, color="sky"))
canvas¶
A colour half-block canvas from a static grid. pixels is a 2-D grid of
colours (name / (r,g,b) / "#rrggbb" / Color), or None for a blank cell.
For an imperative drawing surface, use the Canvas class below.
from maya_py import canvas, show
red, blu = "red", "sky"
show(canvas([
[red, red, None, blu, blu],
[red, None, None, None, blu],
[None, None, "lime", None, None],
]))
cell_grid¶
A full-colour character grid built in one native call. grid is a list of
rows; each cell is None / a 1-char str (that glyph, inherited colour),
(ch, fg), or (ch, fg, bg), where fg/bg is a packed 0xRRGGBB int, an
(r, g, b) tuple, or None for unset. width/height fix the grid size
(short rows are padded, extra cells clipped).
Unlike canvas (which paints half-block pixels), cell_grid places a real
character in every cell — it's the native replacement for the hand-rolled
col(*[row(*specs) for ...]) pattern, allocating one run-merged TextElement
per row instead of one element per cell (pixel-identical output, far less
Python dispatch). Reach for it when drawing dense text panels / ASCII art with
per-cell colour.
from maya_py import cell_grid, show
show(cell_grid([
[("#", "red"), ("#", "orange"), ("#", "yellow")],
["+", ("@", (0, 200, 255), "#101018"), None],
], width=3, height=2))
The Canvas class — imperative drawing¶
A stateful half-block drawing surface (maya's PixelCanvas). The resolution is
width × (height*2) pixels — each terminal cell stacks two vertical pixels,
so a 40×10 canvas is 40×20 pixels. Draw imperatively, then drop .element()
(or feed it through col(...)) into a layout. The drawing methods return
self, so they chain.
Properties
| Property | Returns |
|---|---|
width |
cell width (= pixel width) |
height |
cell height |
pixel_height |
pixel height (= height * 2) |
Methods
| Method | Effect |
|---|---|
set_pixel(x, y, color) |
set the pixel at (x, y), y ∈ [0, height*2) |
line(x1, y1, x2, y2, color) |
Bresenham line |
rect(x, y, w, h, color) |
outline rectangle, w×h pixels |
fill(color) |
flood the whole canvas |
clear() |
reset every pixel to the clear colour (black) |
element() |
build the current drawing into an Element |
from maya_py import Canvas, col, show
c = Canvas(40, 10) # 40×20 pixels
c.fill("black")
c.line(0, 0, 39, 19, "sky")
c.rect(5, 4, 12, 8, "lime")
c.set_pixel(20, 10, "red")
show(col("drawing:", c.element()))
See examples/paint.py
and examples/canvas.py.
For a size-aware drawing that fills its box every frame, prefer the
pixel_canvas / halfblock helpers (they get the live measure pass — see the
note in concepts.md §8).
Scrolling¶
Scrolling composes from a ScrollState (held in your app state) dropped into a
viewport (which clips content) and an optional scrollbar (which visualises
position). The big payoff: scroll states auto-dispatch — the run loop
forwards arrows / PgUp / PgDn / Home / End and (with mouse=True) the wheel and
scrollbar drag to every on-screen scroll state with no handler code. The
full story is concepts.md §6; the API:
scroll_state (state helper, not a renderer)¶
A fresh scroll position (x/y offsets + max bounds). auto_dispatch is on by
default. Set state.auto_dispatch = False only if you route events yourself
with scroll_handle (doing both double-scrolls).
viewport¶
viewport(content: Element, state: ScrollState, *, width: int = 0, height: int = 0,
grow: float = 0.0) -> Element
Clips content to a width×height window scrolled by state. 0 on an axis
means "fill available space"; grow=1 expands the window to fill its row/column
(pushing a sibling scrollbar to the edge). The renderer writes the max scroll
bounds back into state each frame.
scrollbar¶
scrollbar(state: ScrollState, viewport_size: int, *, axis: str = "y", style=None,
thumb_color=None, track_color=None) -> Element
A scrollbar reflecting state over a viewport_size-cell track. axis is
"y" (vertical) or "x" (horizontal). style is a preset name (see
ScrollbarStyle below) or a ScrollbarStyle; thumb_color / track_color
override colours.
scroll_handle (state helper, not a renderer)¶
Manually route an App event to state (arrows / PgUp / PgDn / Home / End +
wheel + drag). Returns True if consumed. Only needed when driving the
low-level run loop yourself or with auto_dispatch = False.
Putting it together
from maya_py import App, scroll_state, viewport, scrollbar, row, col
app = App("log", quit_keys=("q",), sc=scroll_state())
@app.view
def view(s):
body = col(*[f"line {i}" for i in range(200)])
return row(
viewport(body, s.sc, grow=1, height=20),
scrollbar(s.sc, 20, style="neon"),
)
# app.run() # arrows / PgUp / wheel scroll with no handler code
Examples: examples/scroll.py, examples/scroll_styles.py.
The enums¶
Every option that takes a string also accepts the corresponding enum member,
re-exported from the top-level package (from maya_py import GaugeStyle, …).
Values copied from source:
| Enum | Members | Used by |
|---|---|---|
GaugeStyle |
Arc, Bar |
gauge(style=) |
ColumnAlign |
Left, Center, Right |
table column tuples |
ButtonVariant |
Default, Primary, Danger, Ghost |
button(variant=) |
TaskStatus |
Pending, InProgress, Completed |
timeline event status |
ToastLevel |
Info, Success, Warning, Error |
toast message level |
TodoItemStatus |
Pending, InProgress, Completed |
todo_list item status |
TodoListStatus |
Pending, Running, Done, Failed |
todo_list card status |
PopupStyle |
Info, Warning, Error |
popup(style=) |
BannerLevel |
Info, Success, Warning, Error |
system_banner(level=) |
ToolCallStatus |
Pending, Running, Completed, Failed, Confirmation |
tool_call(status=) |
ToolCallKind |
Read, Edit, Execute, Search, Delete, Move, Fetch, Think, Agent, Other |
tool_call(kind=) |
FileChangeKind |
Created, Modified, Deleted, Renamed |
file_changes / changes_strip kind |
TurnRole |
User, Assistant, System, Tool |
turn_divider(role=) |
CursorStyle |
Block, Dots, Bar, Pulse |
streaming_cursor(style=) |
SearchKind |
Grep, Glob |
search_result(kind=) |
SearchStatus |
Pending, Searching, Done, Failed |
search_result(status=) |
ScrollbarStyle |
line, block, slim, heavy, double_line, dotted, dashed, braille, ascii, shadow, minimal, neon, retro, danger, pixel |
scrollbar(style=) |
ScrollbarStyle members are factory functions returning a configured style
object (with settable .thumb_color / .track_color). The names accepted by
scrollbar(style="...") are those member names, plus the alias "double" for
double_line.
from maya_py import button, ButtonVariant, scrollbar, scroll_state, ScrollbarStyle
button("Ship it", variant=ButtonVariant.Primary)
st = ScrollbarStyle.neon()
# override the preset's thumb color via the scrollbar() kwarg, which resolves
# name / "#rrggbb" / (r,g,b). (Setting st.thumb_color directly needs a Color
# object, e.g. maya_py.Color.rgb(...), so prefer the kwarg.)
scrollbar(scroll_state(), 20, style=st, thumb_color="magenta")
Where to go next¶
- concepts.md — the mental model (why widgets are values, the
performance lever of
memo, the auto-dispatch scroll model in §6). - layout.md —
card/row/col/grow/tcoland how widgets flow inside them. - apps.md — wiring controls and scroll states into an interactive loop.