Skip to content

Components

Components live in tui.components:, keyed by name, and are placed on screens by layout nodes (see app.md). A component is either static, holding content declared in the YAML, or source-bound, populated from a data.sources entry named by source: after every fetch.

Kind Shows Static content Source-bound mapping
list one string per row items: item:
table rows and columns rows: each column's value:
logview appended lines, searchable lines: none (lines as-is)
textview one block of text content: none (text as-is)
tree a hierarchy, expandable root: label: + children: or group_by:
inspector label/value pairs, nested fields: each field's path:, or auto:
eventlog a timeline of paged events or lines — key: + text: (windowed sources only)

Common fields

These apply to every kind unless the column says otherwise.

Field Type Default Valid on/with What it does
type string — required The kind: list, table, logview, textview, tree, inspector or eventlog.
title template — — Shown on the pane border.
source string — — The data.sources entry that populates this component. When set, static content (items, rows, lines, content, root, fields values) is ignored.
colors map — — Per-component color overrides. See colors.
color_rules list — list, logview, eventlog Colors each item or line by its value. Tables put rules on each column and inspectors on each field. See color rules.
filterable bool false list, table, inspector, eventlog / opens a filter that narrows the rows (f on an eventlog, where / is search).
searchable bool false logview, textview, tree, eventlog / opens a search that highlights matches; n / N move between them.
filter_placeholder template — any filterable or searchable kind Hint shown inside the empty filter or search input.
initial_filter template — list, table Starts with this filter applied.
initial_query template — logview, textview, tree, inspector Starts with this search or filter query applied.
initial_cursor int 0 list, table, tree, inspector Row the cursor starts on.
loading_label template — list, table, tree Shown beside the spinner while the component has no data yet, e.g. loading pods….
markable bool false list, table, tree Multi-select: adds a mark gutter and binds x (toggle), X (extend from the last mark), A (all), D (none). Actions with multi: true then run once per marked row.
mark_key path — source-bound markable table; span table; table with activity The stable identity marks and activity are held by, read from the source item (not the rendered cells), e.g. metadata.uid. Required on a markable source-bound table, on every span table and on a table with activity.
activity map — table, list, tree on a fetched or polled source Shows a spinner on rows the data says are being worked on. See activity.
on_cursor map — source-bound target Refetches this component's source as another component's cursor moves. See on_cursor.
on_input map — source-bound target Refetches this component's source when an input or toggle on the screen commits. See on_input.

Validation:

  • type is one of the nine kinds; source: names a defined entry.
  • loading_label, line_numbers, gutter and new_for are errors on a kind their row doesn't list.
  • markable only on list, table, tree. On a table bound to a source, mark_key is required. It isn't accepted on lists (they key on the item string), trees (the node path), static tables (row position) or without markable: true. It isn't defaulted to the first column because a non-unique column collapses rows onto one mark and a volatile one (AGE, STATUS) loses the marks on the next poll.
  • markable with a windowed source is an error: a window holds rows without keys. The exception is a span table (bound to an Anchored source), whose rows are keyed, so marking works.

list

One string per row.

Field Type Default Valid on/with What it does
items list of template — static The rows.
item string — required when source-bound Dot-path to each element's display string.
regions:
  type: list
  title: Regions
  source: regions
  item: name
  filterable: true

table

Rows and columns, with sorting, filtering and horizontal scroll.

Field Type Default Valid on/with What it does
columns list — required Column definitions. See columns.
rows list of list — static Each row is a list of cells. A cell is a string, {value, color} for a colored cell, or {label, url} for a hyperlink.
initial_sort map — — Sort to start with: column (a column title, case-insensitive prefix, or a 1-based number) and desc (bool). Only sortable columns match; anything else is ignored.
max_items int 5000 span table Rows held at once; the end furthest from the view is trimmed.
sort_debounce duration tuilib's (400ms) windowed source How long sort input must go quiet before the source is asked for the new order, so stepping through columns with [ / ] sends one request. 0 sends every change.
max_rows int 100 streaming source, no row_key Ring-buffer size: each arriving event is prepended as a row and the oldest drop past this size. -1 is unbounded.
row_key path — streaming source Keyed-upsert mode: an event whose key matches a row updates it in place; a new key appends. Without it, a streaming table is a ring buffer. Distinct from mark_key.

How a table fills depends on its source:

  • Fetched or polled: the rows are replaced on each fetch; cursor, filter, sort and marks survive.
  • Streaming (follow: true, websocket): each event adds or updates a row, per max_rows / row_key.
  • Windowed (the source declares window:): the table holds only the rows on screen and the server answers the filter and sort. It switches to remote filtering and sorting automatically. Committing a filter or sort keeps the current rows on screen, dimmed, until the answer lands; a failed query keeps the rows and the typed filter, and r retries. Each query's outcome is logged to the output console under the source's name. See sources.md.
  • Span (the source is Anchored): the table holds a run of rows walked from an edge, oldest at the top and newest at the bottom, growing as you scroll towards either end and trimmed past max_items. It opens at the end window.anchor: names. Rows are keyed by their cursor and mark_key, so mark_key is required and marking works. The order is the walk's, so columns can't be sorted; the filter is remote, and committing one re-walks from the anchor.

Validation: columns is required. When source-bound, every column needs value:. On a windowed table, a sortable column needs window.sort_param:. sort_debounce is only accepted on a windowed table, since a local sort is instant. A span table needs mark_key, and rejects sortable columns, initial_sort and sort_debounce; max_items is only accepted on span tables (and eventlogs).

Columns

Field Type Default Valid on/with What it does
title template — required Header text. Also the name used by ${selection.Title}, ${cursor.Title}, title:value filter terms and window.filters.
value path — required when source-bound Dot-path to the cell value in each item. A list of paths is tried in order and the first non-empty wins.
width int 0 — 0 sizes to content; a positive number is a fixed width.
flex int 0 — Share of leftover width.
max_width int — — Caps a flex or auto column.
align string left — left, right or center.
sortable bool false — The column can be sorted from the keyboard.
sort string string sortable Comparator: string (case-insensitive), number (commas stripped), si (number with K/M/B/G/T suffix).
hidden bool false — Not drawn, but still in the row: it matches filters and resolves in ${selection.*} / ${cursor.*}. For identity columns a drilldown needs.
distinct list or map — windowed tables The values this column's filter suggests and completes, which otherwise come only from the page that's loaded. A list ([Running, Pending, Failed]), or {source: NAME} for a source that returns a list of strings; it's fetched and polled alongside the table. The source can't be windowed.
color_rules list — — Colors each cell by its value. See color rules.
metric map — source-bound tables; not with value, color_rules or sortable Draws the cell as a badge, ratio, progress bar or sparkline. See metrics.
pods_table:
  type: table
  source: pods
  filterable: true
  markable: true
  mark_key: metadata.uid
  initial_sort: {column: Restarts, desc: true}
  columns:
    - {title: Name, value: metadata.name, flex: 1}
    - {title: Namespace, value: metadata.namespace, hidden: true}
    - title: Status
      value: [status.containerStatuses.0.state.waiting.reason, status.phase]
      color_rules:
        - {when: Running, color: green}
        - {when: "~Err|Crash", color: red}
    - {title: Restarts, value: status.containerStatuses.0.restartCount, align: right, sortable: true, sort: number}

Metrics

metric: on a column or an inspector field draws a small inline summary instead of text. It takes exactly one kind key, whose inputs are dot-paths into the row's item, in this order. Compute anything that isn't already a field with a derive operator.

Field Type Default Valid on/with What it does
badge list of 3 paths — one kind key Status counts, ok / warn / down: 12✓ 3⚠ 1✗. Zero counts are left out.
ratio list of 2 paths — one kind key Done / total, 3/5: green when complete, yellow partway, red at zero.
bar path — one kind key A value drawn as a progress bar against max.
spark path — one kind key A list of numbers drawn as a sparkline. The data must hold the series.
max number or path — bar, required The bar's full value.
width int the column's width, else 10 bar, spark Width in cells.
color color severity colors (bar), blue (spark) bar, spark A fixed color: a name or a 0–255 index.

${selection.<column>} and ${cursor.<column>} read a metric column's first input, not its glyphs, so ratio: [ready, desired] binds the ready count.

columns:
  - {title: Ready,  metric: {ratio: [status.readyReplicas, spec.replicas]}}
  - {title: Health, metric: {badge: [ok, warn, down]}}
  - {title: CPU,    metric: {bar: cpu_pct, max: 100, color: blue}}
  - {title: Trend,  metric: {spark: latency_series}}

Validation: exactly one kind key, with the right number of paths; a bar needs max, and max, color and width are errors on the other kinds. A metric needs a source-bound table or inspector, and can't share a column with value, color_rules or sortable: true (or a field with value, path or color_rules). Lists and trees have no metrics: a row there is one string, and a table already puts a metric next to text.

logview

Lines appended over time, with search and an optional filter mode. The natural target for streaming sources.

Field Type Default Valid on/with What it does
lines list of template — static Initial lines.
max_lines int 10000 — Lines kept before the oldest drop. -1 is unbounded.
filter_mode bool false searchable Starts in filter mode: the search hides non-matching lines instead of highlighting matches.
line_numbers bool false — Numbers lines in a gutter, from the start of what's held. Numbers locate a line on screen; they shift as old lines drop.

When source-bound, a format: text body is split on newlines, a JSON list of strings is used as-is, and a streaming source appends one line per event. When a stream ends, the log gets a dimmed — stream ended — line; r reconnects it and adds — reconnected —. r leaves a live stream alone.

pod_logs:
  type: logview
  title: Logs
  source: pod_logs        # http with follow: true
  searchable: true
  color_rules:
    - {when: "~ERROR", color: red}

textview

One block of text, replaced on each fetch. For help panes, command output and documents.

Field Type Default Valid on/with What it does
content string — static The text. Replaced by the source's value when source-bound.
wrap bool false — Word-wrap. w toggles it at runtime.

When source-bound, a format: text body passes through verbatim and a JSON value is shown formatted.

help:
  type: textview
  title: Help
  searchable: true
  wrap: true
  content: |
    Press / to search, w to toggle wrap.

tree

A hierarchy with expand and collapse. Static trees declare root:; source-bound trees build nodes from the data.

Field Type Default Valid on/with What it does
root node — required when static The root node: label (template) and children (a list of nodes).
label path — required when source-bound Dot-path to each node's display label.
children path — source-bound; not with group_by Dot-path on each node to its list of child nodes, for nested data (filesystem trees, org charts). Nodes with none are leaves.
group_by path — source-bound; not with children Buckets a flat list by this value; each bucket becomes a parent node labelled with it (e.g. group_by: kind).
root_label template title, then the source name source-bound Label of the root node. Kept stable across refreshes so expansion state survives.
initial_depth int 0 — Expands every node shallower than this: 0 root only, 1 root expanded, 2 one level more.

Cursor and expansion state survive refreshes.

by_team:
  type: tree
  title: People
  source: people
  label: name
  group_by: team
  initial_depth: 1
  searchable: true

inspector

Label/value pairs for one record, with nested groups that expand.

Field Type Default Valid on/with What it does
fields list — not with auto The rows. See inspector fields.
auto bool false source-bound; not with fields Builds the rows from whatever the source returns: maps expand into groups, arrays into [0], [1]…, scalars render as-is.
initial_depth int 0 — Expands every group shallower than this.

Inspector fields

Field Type Default Valid on/with What it does
label template — required The left-hand text.
value template — — Static right-hand text. Leave empty for a group header.
path string — source-bound Dot-path into the source's value; overrides value.
color_rules list — — Colors the value. See color rules.
children list — — Nested fields, shown as an expandable group.
metric map — source-bound; not with value, path or color_rules Draws the value as a badge, ratio, bar or sparkline. See metrics.
pod_detail:
  type: inspector
  source: pod
  fields:
    - {label: Name, path: metadata.name}
    - label: Status
      children:
        - {label: Phase, path: status.phase, color_rules: [{when: Running, color: green}]}
        - {label: Node, path: spec.nodeName}

eventlog

A timeline of events or lines read in pages from a source too large to hold: an AWX job's events, a search index, a long log. Each item draws as the lines of its text. Unlike a logview, which holds a stream whole, an eventlog binds only to a windowed source and fetches as you scroll. Over an Anchored source it opens at the end window.anchor: names and walks from there.

Field Type Default Valid on/with What it does
key path — required Each item's identity. Bare ${selection} when enter fires an action.
text path — required The text an item draws as, split on newlines. An empty value draws as a dim "· no output" line: the item still happened, and enter still opens it.
mark path — — Shown as-is in the gutter, normally the server's timestamp.
start string oldest paged (not Anchored) sources Where the view opens once the source isn't growing: oldest or newest. A growing source always opens at the newest item and follows. An Anchored source uses window.anchor: instead.
max_items int tuilib's — Items held at once; the end furthest from the view is trimmed.
gutter bool true — The gutter of positions down the left. false hides it.
new_for duration 2s — How long items that just loaded stay marked in the gutter. - never marks them.

Keys: / searches the items held, and n / N jump between matches. f filters (narrowing the query the source answers) when filterable: is set. G goes to the newest item and follows it; moving away while the source is growing counts new items as "↓ N new" instead of moving you.

Enter (or a double click) fires the screen's key: enter action if one is bound with this eventlog as from:, with the item as ${selection}: bare ${selection} is the key, and every field of the item is ${selection.PATH} (${selection.event}, ${selection.event_data.host}). With no action bound, enter opens the item's raw data in an inspector; esc closes it.

Validation: source: must declare window:; key and text are required; start is oldest or newest. These fields are rejected on other kinds. An eventlog can't be markable.

events:
  type: eventlog
  title: Job events
  source: job_events      # declares window:
  key: id
  text: stdout
  mark: created
  searchable: true
  color_rules:
    - {when: "~^fatal|FAILED", color: red}

input

A one-line text field. It doesn't display a source; other components read its committed value as ${input.NAME} (NAME is the component's name) in on_input / on_cursor binds and in actions.

Field Type Default Valid on/with What it does
initial template — — The value before anything is committed. It's bound for the first fetch.
placeholder template — — Shown while the field is empty.
char_limit int 0 (none) — The longest value accepted.
mask bool false — Shows typed characters as bullets (app.glyphs.mask).

Tab onto the field to edit it; while it's being edited every key is typing, so q and esc don't reach the app. enter commits the value, and every component that reads it refetches. esc puts back the committed value and leaves the field; enter starts editing again. Tabbing away puts back anything typed but not committed.

toggle

A yes/no switch, read as ${input.NAME}: true or false. Every flip (←/→, space or a click) commits, and the components that read it refetch.

Field Type Default Valid on/with What it does
initial string false — true or false.
yes_label string yes — The "true" side's label.
no_label string no — The "false" side's label.

An input or toggle is three rows tall: in a vstack it takes fixed: 3 on its own, and any other height is an error. In an hstack, flex / fixed set its width as usual.

search:       {type: input,  title: Search, placeholder: name…, initial: "${env.Q}"}
show_deleted: {type: toggle, title: Deleted?, yes_label: shown, no_label: hidden}
pods_table:
  type: table
  source: pods            # declares parameters: q, deleted
  on_input:
    bind: {q: "${input.search}", deleted: "${input.show_deleted}"}

Validation: neither takes source:; initial on a toggle is true or false; char_limit can't be negative; their fields are errors on other kinds.

activity

Shows a spinner on the rows that are being worked on, according to the data: a pod Pending, a sync Running, a job queued. The server is the source of truth. Every fetch re-evaluates every row, so a row starts spinning when a read says it's busy and stops when a read says it isn't. Without activity: nothing changes; with it, a busy row shows the spinner and its status (⣾ Pending) in the activity column, and the rest render as usual.

Field Type Default Valid on/with What it does
status path — required The field that says what a row is doing. Its value is shown beside the spinner while the row is busy.
busy list of string — not with busy_when The status values that mean busy, matched exactly.
busy_when expr — not with busy An expression over the row's item, true while it's busy, for when busy-ness isn't one field's value: status.observedGeneration < generation.
column string the column whose value: is status table The column the spinner draws in. A list or tree draws it beside the row.
revision path — — A value that moves when work on the row finishes, e.g. metadata.resourceVersion. With it, a request whose status ends where it began (Synced, then Synced) still ends the spinner as done, and the report can say whether anything changed. Without it, the spinner waits out the claim's settle.
pods_table:
  type: table
  source: pods            # refresh: 5s
  mark_key: metadata.uid
  activity:
    status: status.phase
    busy: [Pending, Terminating]
  columns:
    - {title: Name,  value: metadata.name}
    - {title: Phase, value: status.phase, width: 14}   # the spinner draws here

Give the activity column a width: that fits the spinner and the longest busy status; the indicator never widens a column.

Reads failing. When a polled source's fetches start failing, every component bound to it shows · reads failing on its title until a fetch succeeds again, and the error is reported once rather than on every poll. Rows keep what the last good read said; rows that were busy then show a static, muted marker instead of spinning. This applies to every polled component, with or without activity:.

Validation: status and exactly one of busy / busy_when are required, and busy_when must compile. A table needs mark_key: (activity is tracked per row) and a column to draw in. Activity isn't available on streaming sources (one event isn't a whole read of the rows) or windowed ones (their rows have no keys).

Color rules

A rule pairs a matcher with a color. Rules are tried in order and the first match colors the text; with no match it renders plain. The selected-row background passes through.

Field Type Default Valid on/with What it does
when string — (always matches) — Exact text (case-insensitive), ~regex (case-insensitive), or a numeric comparison: > 5, <= 10, == 0, != 0, with an optional K/M/B/G/T suffix on the number. Empty matches everything, which makes a final default rule.
color color — required See color values.

A theme: color in a rule is re-rendered when the theme cycles. On a windowed table or an eventlog, rows already loaded keep their colors until the next page is fetched.

colors

Overrides individual theme colors on one component. Unset fields keep the theme's. Values are color values, including theme:<token>, which follows the palette when themes cycle.

Field Type Default Valid on/with What it does
border_active color theme all Border color when focused.
border_inactive color theme all Border color when unfocused.
spinner color theme all The loading spinner.
selected color theme list The focused item.
selected_fg color theme table, eventlog Focused row text.
selected_bg color theme table, eventlog Focused row background.
header color theme table Header text.
cell color theme table, eventlog Ordinary cell text.
column_separator color theme table The line between columns. Palette colors only (named or 0–255).
header_rule color theme table The line under the header. Palette colors only.
label color theme inspector Field labels.
value color theme inspector Field values.
match color theme inspector, logview, tree Search-match highlight.
current_line_bg color theme inspector, logview, tree Background of the cursor line.
mark color theme list, table, tree The ✓ on a marked row that isn't under the cursor.
gutter color theme logview, eventlog The line-number or position gutter.
new color theme eventlog Positions of items that just loaded.

on_cursor

Refetches this component's source whenever another component's cursor moves, binding the driver's focused row into the source's parameters:. This is the "table on top, detail below" pattern, without pushing a screen. Repeat visits to a row hit the source's param cache.

Field Type Default Valid on/with What it does
source string — required The driver: a table, list or tree on the same screen. (It names a component, not a data source.)
bind map of template — — Maps this component's source parameters to templates over the driver's focused row: ${cursor}, ${cursor.N}, ${cursor.Title}, ${cursor.path}, plus ${env.*}. See templating.md.

Validation: the driver is a table, list or tree placed on the same screen; the component declaring on_cursor must itself have a source:.

repo_detail:
  type: inspector
  source: repo            # declares parameters: owner, name
  auto: true
  on_cursor:
    source: repos_table
    bind:
      owner: ${cursor.Owner}
      name:  ${cursor.Repo}

${input.NAME} works in on_cursor.bind too, so one source can follow a cursor and an input at once.

on_input

Refetches this component's source when an input or toggle on the same screen commits, binding their values into the source's parameters:. Use it on a component with no cursor driver; it's fetched with the inputs' initial values when the screen opens.

Field Type Default Valid on/with What it does
bind map of template — required Maps source parameters to templates over ${input.NAME}, plus ${env.*}.

Validation: the component has a source: that declares each bound parameter; ${input.NAME} names an input or toggle on this screen; a parameter can't be bound by both on_input and on_cursor.