Table & List Updates¶
This tutorial covers building interactive, live-updating tables and lists — the backbone of most TUI dashboards.
Basic Static Table¶
from pyratatui import (
Terminal, Table, Row, Cell, TableState,
Constraint, Block, Style, Color,
)
headers = Row([
Cell("Name").style(Style().bold()),
Cell("Role").style(Style().bold()),
Cell("Status").style(Style().bold()),
])
data = [
Row.from_strings(["Alice", "Engineer", "Active"]),
Row.from_strings(["Bob", "Designer", "Away"]),
Row.from_strings(["Carol", "Manager", "Active"]),
]
state = TableState()
state.select(0)
with Terminal() as term:
while True:
snap_state = state
def ui(frame, _state=snap_state):
frame.render_stateful_table(
Table(data,
widths=[Constraint.percentage(35),
Constraint.percentage(35),
Constraint.fill(1)],
header=headers)
.block(Block().bordered().title("Staff"))
.highlight_style(Style().fg(Color.cyan()).bold())
.highlight_symbol("▶ "),
frame.area,
_state,
)
term.draw(ui)
ev = term.poll_event(timeout_ms=100)
if ev:
if ev.code == "q":
break
elif ev.code == "Down":
state.select_next()
elif ev.code == "Up":
state.select_previous()
Table with Per-Cell Styling¶
Individual cells can carry their own Style, overriding the row style:
def status_color(s: str):
return {
"Active": Color.green(),
"Away": Color.yellow(),
"Offline": Color.red(),
}.get(s, Color.white())
def make_rows(services):
rows = []
for svc in services:
rows.append(Row([
Cell(svc["name"]),
Cell(f"{svc['cpu']}%").style(Style().fg(
Color.green() if svc["cpu"] < 50 else
Color.yellow() if svc["cpu"] < 80 else Color.red()
)),
Cell(f"{svc['mem']}%").style(Style().fg(Color.blue())),
Cell(svc["status"]).style(Style().fg(status_color(svc["status"]))),
Cell(svc["uptime"]),
]))
return rows
Live-Updating Table with Async¶
import asyncio
import random
from pyratatui import (
AsyncTerminal,
Layout, Constraint, Direction,
Table, Row, Cell, TableState,
Block, Style, Color, Paragraph,
BorderType,
)
SERVICES = ["nginx", "postgres", "redis", "kafka", "prometheus"]
def fresh_data():
return [
{
"name": name,
"cpu": random.randint(0, 100),
"mem": random.randint(5, 95),
"status": random.choice(["Running"] * 4 + ["Degraded", "Stopped"]),
"uptime": f"{random.randint(1, 999)}h",
}
for name in SERVICES
]
state = {
"data": fresh_data(),
"table_state": TableState(),
}
state["table_state"].select(0)
async def refresh_loop():
while True:
await asyncio.sleep(1.5)
state["data"] = fresh_data()
def cpu_color(p):
return Color.green() if p < 50 else Color.yellow() if p < 80 else Color.red()
def status_color(s):
return {"Running": Color.green(), "Degraded": Color.yellow(), "Stopped": Color.red()}.get(s, Color.white())
def build_table(data):
header = Row([
Cell(h).style(Style().bold().fg(Color.white()))
for h in ["Service", "CPU %", "MEM %", "Status", "Uptime"]
])
rows = [
Row([
Cell(d["name"]),
Cell(f"{d['cpu']}%").style(Style().fg(cpu_color(d["cpu"]))),
Cell(f"{d['mem']}%").style(Style().fg(Color.blue())),
Cell(d["status"]).style(Style().fg(status_color(d["status"]))),
Cell(d["uptime"]),
])
for d in data
]
return Table(rows,
widths=[Constraint.fill(1)] * 5,
header=header)
async def main():
asyncio.create_task(refresh_loop())
async with AsyncTerminal() as term:
term.hide_cursor()
async for ev in term.events(fps=20):
data = list(state["data"])
ts = state["table_state"]
def ui(frame, _data=data, _ts=ts):
area = frame.area
chunks = (
Layout()
.direction(Direction.Vertical)
.constraints([Constraint.fill(1), Constraint.length(1)])
.split(area)
)
frame.render_stateful_table(
build_table(_data)
.block(Block().bordered()
.title(" Service Monitor (auto-refresh 1.5s) ")
.border_type(BorderType.Rounded))
.highlight_style(Style().fg(Color.cyan()).bold())
.highlight_symbol("▶ ")
.column_spacing(1),
chunks[0],
_ts,
)
frame.render_widget(
Paragraph.from_string(" ↑/↓: Navigate r: Refresh q: Quit")
.style(Style().fg(Color.dark_gray())),
chunks[1],
)
term.draw(ui)
if ev:
if ev.code == "Down":
state["table_state"].select_next()
elif ev.code == "Up":
state["table_state"].select_previous()
elif ev.code == "Home":
state["table_state"].select_first()
elif ev.code == "End":
state["table_state"].select_last()
elif ev.code == "r":
state["data"] = fresh_data()
term.show_cursor()
asyncio.run(main())
List with Navigation¶
List is for single-column scrollable items with optional selection highlighting:
from pyratatui import (
Terminal, List, ListItem, ListState,
Block, Style, Color, BorderType,
)
items = [ListItem(f"Option {i+1}") for i in range(20)]
state = ListState()
state.select(0)
with Terminal() as term:
while True:
def ui(frame, _state=state):
frame.render_stateful_list(
List(items)
.block(Block().bordered()
.title("Menu")
.border_type(BorderType.Rounded))
.highlight_style(Style().fg(Color.yellow()).bold())
.highlight_symbol("▶ "),
frame.area,
_state,
)
term.draw(ui)
ev = term.poll_event(timeout_ms=50)
if ev:
if ev.code == "q":
break
elif ev.code == "Down":
state.select_next()
elif ev.code == "Up":
state.select_previous()
elif ev.code == "Home":
state.select_first()
elif ev.code == "End":
state.select_last()
List with Styled Items¶
Each ListItem can carry its own style:
from pyratatui import ListItem, Style, Color
def make_items(services):
symbols = {"Running": "●", "Degraded": "◐", "Stopped": "○"}
colors = {"Running": Color.green(), "Degraded": Color.yellow(), "Stopped": Color.red()}
return [
ListItem(
f"{symbols[s['status']]} {s['name']:14s} {s['cpu']:3d}%",
Style().fg(colors[s["status"]]),
)
for s in services
]
Synchronized List + Table¶
The full-app pattern keeps ListState and TableState in sync so selecting in the list highlights the same row in the table:
async for ev in term.events(fps=25):
if ev:
if ev.code == "Down":
state["list_state"].select_next()
state["table_state"].select_next()
elif ev.code == "Up":
state["list_state"].select_previous()
state["table_state"].select_previous()
Table API Quick Reference¶
See also: Table reference
| Method | Description |
|---|---|
Table(rows, widths, header=None) |
Constructor |
.block(b) |
Wrap in a Block container |
.style(s) |
Base style for all cells |
.header_style(s) |
Override header row style |
.highlight_style(s) |
Style for the selected row |
.highlight_symbol(sym) |
Prefix for selected row (e.g. "▶ ") |
.column_spacing(n) |
Extra gap between columns |
frame.render_stateful_table(widget, area, state) |
Render with selection |
List API Quick Reference¶
See also: List reference
| Method | Description |
|---|---|
List(items) |
Constructor — list of ListItem |
.block(b) |
Wrap in a Block |
.highlight_style(s) |
Style for selected item |
.highlight_symbol(sym) |
Prefix character(s) |
.direction(dir) |
ListDirection.TopToBottom (default) or BottomToTop |
frame.render_stateful_list(widget, area, state) |
Render with selection |