Rich Python
Design terminal output around a render target and data contract. Rich objects
describe presentation; a Console decides capabilities, dimensions, stream,
and rendering.
Boundary
Use this skill when a Python project already uses Rich or the user requests
Rich terminal output. Do not introduce styling into stdout promised as JSON,
CSV, a pipe protocol, or another machine-readable format. Do not use Rich to
define command arguments (use the CLI framework), to replace structured logs,
or to build a web interface.
Know the objects
| Object |
Meaning |
Use |
Console |
Rendering environment plus output stream |
Own terminal detection, width, color, capture, and printing. |
Text |
Styled text with explicit spans |
Dynamic/untrusted text or programmatic styling. |
| Renderable |
Object implementing Rich's console protocol |
Table, Tree, Panel, Syntax, user-defined views. |
Progress / Status |
Managed transient display |
Bounded work with a lifecycle. |
Live |
Refresh controller for a changing renderable |
Multi-field dashboards or custom updating layouts. |
Group / layout container |
Composition of renderables |
One coherent frame rather than interleaved prints. |
Read the rendering model when output breaks
under redirection, width changes, markup-like data, or tests.
Ordered workflow
- Classify the channel: human stdout, diagnostics stderr, machine stdout,
captured test output, file export, or interactive TTY.
- Identify the semantic structure: prose, records, hierarchy, source, progress,
or a changing dashboard. Choose one matching renderable.
- Create or reuse a
Console at the application boundary. Inject it into code
that must be testable; do not scatter independently configured consoles.
- Convert untrusted/dynamic strings to literal
Text or escape markup. Never
interpolate user text inside markup tags.
- Define width, overflow, wrapping, and empty-state behavior. Tables need
stable column meaning, not one visual column per arbitrary key.
- Put transient output in a context manager so teardown occurs on errors.
- Test at a fixed width with color disabled unless ANSI output itself is the
contract. Test non-TTY behavior for progress/live code.
Intent to renderable
| Intent |
Use |
Avoid |
| One styled message |
Console.print with Text or trusted markup |
Building a one-cell table |
| Aligned records with headers |
Table |
Manual spaces/tabs |
| Nested ownership or paths |
Tree |
Flattened repeated prefixes |
| Framed summary |
Panel around a composed renderable |
ANSI box-drawing strings |
| Source/config excerpt |
Syntax |
Hand-injected color codes |
| Known iterable progress |
track or Progress.track |
Printing every iteration |
| Multiple tasks/columns |
Explicit Progress and task IDs |
Nested independent progress bars |
| Arbitrary changing view |
Live |
Clear-screen loops |
| Machine-readable data |
Plain serialization on stdout |
Rich markup or tables |
Canonical static output
from rich.console import Console
from rich.table import Table
from rich.text import Text
def render_jobs(console: Console, jobs: list[dict[str, object]]) -> None:
table = Table("ID", "State", "Owner", show_header=True)
for job in jobs:
state = str(job["state"])
state_text = Text(state, style="green" if state == "ready" else "yellow")
table.add_row(str(job["id"]), state_text, Text(str(job["owner"])))
console.print(table if jobs else Text("No jobs.", style="dim"))
Text(str(value)) keeps brackets and markup-like input literal. repr-style
highlighting is not a stable serialization format.
Streams and terminal capability
- Reserve stdout for the documented primary result. Send diagnostics to a
console configured with
stderr=True.
- Let Rich auto-detect terminal capability for ordinary execution. Do not force
color or interactive mode to make redirected output look like a TTY.
- Respect
NO_COLOR, dumb terminals, narrow width, and noninteractive CI.
- Use
force_terminal, force_interactive, or a fixed width only when the
caller/test explicitly owns that rendering environment.
- If the command supports
--json, bypass Rich on stdout entirely; Rich may
still render errors to stderr.
Progress and live lifecycle
Choose Progress when task completion is the primary state. Choose Live when
the whole view changes. Keep one active live display per console; print through
its console so messages do not corrupt the frame. Update at meaningful work
boundaries, not every byte or tight-loop iteration. Always use with so final
refresh and cursor restoration occur after success or failure.
Read progress and live rules before combining
tasks, nesting displays, redirecting output, or running in CI.
Deterministic verification
Inject Console(file=StringIO(), width=<fixed>, color_system=None, force_terminal=False) for semantic text tests. Use console.capture() for a
small local capture. Assert content, row order, empty state, and absence of ANSI
codes; avoid full snapshots unless exact layout is contractual. Read testing
terminal output.
Inspect the installed version of Rich and its signatures when a constructor keyword,
environment override, or live behavior may drift. The authoring anchors were
executed on Rich 15.0.0; official rendered docs may show an earlier stable
version. Completion requires correct stream separation, safe dynamic text,
readable narrow/non-TTY output, managed live teardown, deterministic tests, and
no Rich formatting in machine-readable output.
References
- Rendering model and markup safety
- Progress and Live lifecycle
- Testing terminal output
1---2name: rich-python3description: Use for writing, reviewing, debugging, or testing Python terminal presentation built with Rich, including Console streams, Text and markup safety, tables, trees, panels, progress, status, Live displays, render protocols, and deterministic output capture. Do not use for CLI argument parsing, structured logging design, browser UI, or machine-readable protocol output that must remain plain.4---56# Rich Python78Design terminal output around a render target and data contract. Rich objects9describe presentation; a `Console` decides capabilities, dimensions, stream,10and rendering.1112## Boundary1314Use this skill when a Python project already uses Rich or the user requests15Rich terminal output. Do not introduce styling into stdout promised as JSON,16CSV, a pipe protocol, or another machine-readable format. Do not use Rich to17define command arguments (use the CLI framework), to replace structured logs,18or to build a web interface.1920## Know the objects2122| Object | Meaning | Use |23|---|---|---|24| `Console` | Rendering environment plus output stream | Own terminal detection, width, color, capture, and printing. |25| `Text` | Styled text with explicit spans | Dynamic/untrusted text or programmatic styling. |26| Renderable | Object implementing Rich's console protocol | `Table`, `Tree`, `Panel`, `Syntax`, user-defined views. |27| `Progress` / `Status` | Managed transient display | Bounded work with a lifecycle. |28| `Live` | Refresh controller for a changing renderable | Multi-field dashboards or custom updating layouts. |29| `Group` / layout container | Composition of renderables | One coherent frame rather than interleaved prints. |3031Read [the rendering model](references/rendering-model.md) when output breaks32under redirection, width changes, markup-like data, or tests.3334## Ordered workflow35361. Classify the channel: human stdout, diagnostics stderr, machine stdout,37 captured test output, file export, or interactive TTY.382. Identify the semantic structure: prose, records, hierarchy, source, progress,39 or a changing dashboard. Choose one matching renderable.403. Create or reuse a `Console` at the application boundary. Inject it into code41 that must be testable; do not scatter independently configured consoles.424. Convert untrusted/dynamic strings to literal `Text` or escape markup. Never43 interpolate user text inside markup tags.445. Define width, overflow, wrapping, and empty-state behavior. Tables need45 stable column meaning, not one visual column per arbitrary key.466. Put transient output in a context manager so teardown occurs on errors.477. Test at a fixed width with color disabled unless ANSI output itself is the48 contract. Test non-TTY behavior for progress/live code.4950## Intent to renderable5152| Intent | Use | Avoid |53|---|---|---|54| One styled message | `Console.print` with `Text` or trusted markup | Building a one-cell table |55| Aligned records with headers | `Table` | Manual spaces/tabs |56| Nested ownership or paths | `Tree` | Flattened repeated prefixes |57| Framed summary | `Panel` around a composed renderable | ANSI box-drawing strings |58| Source/config excerpt | `Syntax` | Hand-injected color codes |59| Known iterable progress | `track` or `Progress.track` | Printing every iteration |60| Multiple tasks/columns | Explicit `Progress` and task IDs | Nested independent progress bars |61| Arbitrary changing view | `Live` | Clear-screen loops |62| Machine-readable data | Plain serialization on stdout | Rich markup or tables |6364## Canonical static output6566```python67from rich.console import Console68from rich.table import Table69from rich.text import Text707172def render_jobs(console: Console, jobs: list[dict[str, object]]) -> None:73 table = Table("ID", "State", "Owner", show_header=True)74 for job in jobs:75 state = str(job["state"])76 state_text = Text(state, style="green" if state == "ready" else "yellow")77 table.add_row(str(job["id"]), state_text, Text(str(job["owner"])))78 console.print(table if jobs else Text("No jobs.", style="dim"))79```8081`Text(str(value))` keeps brackets and markup-like input literal. `repr`-style82highlighting is not a stable serialization format.8384## Streams and terminal capability8586- Reserve stdout for the documented primary result. Send diagnostics to a87 console configured with `stderr=True`.88- Let Rich auto-detect terminal capability for ordinary execution. Do not force89 color or interactive mode to make redirected output look like a TTY.90- Respect `NO_COLOR`, dumb terminals, narrow width, and noninteractive CI.91- Use `force_terminal`, `force_interactive`, or a fixed width only when the92 caller/test explicitly owns that rendering environment.93- If the command supports `--json`, bypass Rich on stdout entirely; Rich may94 still render errors to stderr.9596## Progress and live lifecycle9798Choose `Progress` when task completion is the primary state. Choose `Live` when99the whole view changes. Keep one active live display per console; print through100its console so messages do not corrupt the frame. Update at meaningful work101boundaries, not every byte or tight-loop iteration. Always use `with` so final102refresh and cursor restoration occur after success or failure.103104Read [progress and live rules](references/progress-live.md) before combining105tasks, nesting displays, redirecting output, or running in CI.106107## Deterministic verification108109Inject `Console(file=StringIO(), width=<fixed>, color_system=None,110force_terminal=False)` for semantic text tests. Use `console.capture()` for a111small local capture. Assert content, row order, empty state, and absence of ANSI112codes; avoid full snapshots unless exact layout is contractual. Read [testing113terminal output](references/testing-output.md).114115Inspect the installed version of Rich and its signatures when a constructor keyword,116environment override, or live behavior may drift. The authoring anchors were117executed on Rich 15.0.0; official rendered docs may show an earlier stable118version. Completion requires correct stream separation, safe dynamic text,119readable narrow/non-TTY output, managed live teardown, deterministic tests, and120no Rich formatting in machine-readable output.121122## References123124- [Rendering model and markup safety](references/rendering-model.md)125- [Progress and Live lifecycle](references/progress-live.md)126- [Testing terminal output](references/testing-output.md)