Skip to content

Components

A component is a pane on screen. Declare it under tui.components: with a name and a type:, give it static content or a source:, and place it in a layout.

Kind Shows Fill it with
list one string per row items:, or source: + item:
table rows and columns rows:, or source: + a value: per column
logview lines that keep arriving lines:, or a (usually streaming) source
textview one block of text content:, or a source
tree a hierarchy root:, or source: + label: and children: or group_by:
inspector one record as label/value pairs fields:, or source: with auto: true
eventlog a long timeline of events or lines, read a page at a time a windowed source + key: and text:

Every field is in Reference → Components.

Binding to data

A bound component re-renders after every fetch. Cursor, filter, sort, marks and tree expansion survive refreshes, so polling a source every few seconds doesn't disturb the user.

Tables pluck each cell with a dot-path. A list of paths is a fallback chain, tried until one gives a value:

pods_table:
  type: table
  source: pods
  columns:
    - {title: Name, value: metadata.name}
    - title: Status
      value: [status.containerStatuses.0.state.waiting.reason, status.phase]

What a table does with a streaming source depends on row_key:. Without it, each event is a new row at the top (a live tape, capped by max_rows:); with it, an event updates the row with the same key (a status board).

Filter, search and sort

filterable: true (list, table, inspector, eventlog) makes / narrow the rows (on an eventlog the key is f). Terms are space-separated and all must match:

Term Matches
nginx any cell containing "nginx"
region:europe the column whose title starts with "region"
~^web-\d+ a case-insensitive regex
status:~crash a regex in one column

searchable: true (logview, textview, tree, eventlog) makes / highlight matches instead; n and N move between them.

Columns marked sortable: true can be sorted: [ and ] step the sort through the sortable columns, and s flips the direction. sort: number or sort: si (2K, 1.5M) compare numbers properly.

Color

color_rules: colors values by what they say: on each column for tables, on each field for inspectors, and on the component for lists and logviews.

color_rules:
  - {when: Running, color: green}       # exact text
  - {when: "~Crash|Error", color: red}  # regex
  - {when: "> 5", color: yellow}        # number
  - {when: "", color: gray}             # everything else

colors: overrides individual theme colors on one component. A theme:accent-style value follows the palette when themes cycle.

Selecting several rows

markable: true (list, table, tree) adds a mark gutter: x toggles a row, X extends from the last mark, A marks all, D clears. Actions with multi: true then run once per marked row.

Marks are held by key so they stay on the right rows when a poll reorders them. A source-bound table names that key with mark_key:, a field that's stable and unique per row (such as metadata.uid), even if no column shows it.

Detail that follows the cursor

on_cursor: refetches a component's source whenever another component's cursor moves, without opening a new screen:

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

Rows already visited are served from the source's parameter cache.

Rows that are being worked on

activity: makes rows spin while the data says work is running on them: a pod Pending, a sync Running. Every poll re-reads every row, so a row starts and stops spinning when the server says so.

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}

When a polled source's reads start failing, its panes say · reads failing on the title until a read succeeds, and the error is reported once. Actions can make their rows spin straight away with a claim.

Long timelines: the eventlog

A job's events, a search index, an application log: too long to hold, so an eventlog reads them a page at a time from a windowed source and fetches more as you scroll. Each item draws as the lines of its text:.

events:
  type: eventlog
  source: job_events      # declares window:
  key: id
  text: stdout
  mark: created
  searchable: true        # / searches what's loaded
  filterable: true        # f narrows on the server

Enter on an item runs a key: enter action with the item as ${selection}, or, with none bound, opens its raw fields in an inspector. A source that's still growing (a running job) makes the eventlog follow the newest item; see data.

Tables over Anchored sources

A table bound to an Anchored source is a span table: it holds a run of rows walked from the newest (or oldest), grows as you scroll towards either end, and is capped by max_items:. Rows are keyed by mark_key:, which it requires, so marks hold as more rows load. Its order is the walk's, so columns can't be sorted.