The Problem
Developers need a way to build rich, interactive terminal (and optional web) interfaces without the overhead of full‑stack UI toolkits. Existing solutions either require heavy JavaScript stacks or expose low‑level terminal control APIs that are hard to compose.
What This Does
textual supplies a Python‑first UI framework that maps familiar web concepts (CSS layout, widgets, events) onto terminal rendering.
- Core runtime lives in
src/textual/– e.g.src/textual/app.pydefines theAppclass and therunentry point (line 2296). - UI elements are in
src/textual/widgets/andsrc/textual/css/, while the rendering pipeline is insrc/textual/_compositor.pyandsrc/textual/_wrap.py. - Documentation, examples and a demo application are shipped under
docs/andexamples/, so a new project can be bootstrapped fromexamples/calculator.pyor the built‑in demo (python -m textual).
How It Is Wired
Execution starts at src/textual/app.py:run which constructs an App instance, parses command‑line options, and invokes the internal event loop. From there the call graph shows the most‑used hubs:
query_one– called from 149 places, resolves a widget by CSS selector.post_message– 96 callers, propagates events through the widget tree.refresh– 58 callers, triggers a redraw of the compositor.
These hubs sit in src/textual/app.py and src/textual/dom.py, making those files high‑impact change locations (they each import/export 57 other modules). The rendering path proceeds:
run→compose(user‑overridden) →Label,Button,Static, etc. (viacompose -> Label x302,compose -> Button x101).- Widgets emit events (
post_message) that travel up the DOM (src/textual/dom.py). - The compositor (
src/textual/_compositor.py) consumes layout data, callsrefresh, and finally writes the terminal buffer.
External effects are limited: a single filesystem read occurs when src/textual/app.py:_check_recompose loads a Markdown file (Path(__file__).with_suffix(".md").read_text). No network calls or cryptographic operations are present in the core path.
The import graph contains 988 internal modules with only 5 import edges and zero circular dependencies, indicating a clean modular structure. However, three files (_compositor.py, _wrap.py, _xterm_parser.py) have deep nesting (max indentation 8), which can hinder readability.
How To Use It
# Clone the repo (required for development)
git clone https://github.com/moses-y/textual.git
cd textual
# Install dependencies with Poetry (pyproject.toml)
poetry install # creates a virtualenv and installs all runtime + test deps
# Run the bundled demo
python -m textual
# Or launch a specific example
python examples/calculator.py
No additional configuration files are required; the framework reads its CSS from the CSS attribute on App subclasses. The CI workflow (.github/workflows/pythonpackage.yml) runs the test suite with pytest and builds a distribution via poetry build.
Real‑World Use
A monitoring service can embed a live dashboard in its CLI tool:
from textual.app import App
from textual.widgets import DataTable
class StatusApp(App):
def compose(self):
table = DataTable()
table.add_columns("Host", "CPU", "Mem")
# populate rows from an async API
yield table
StatusApp().run()
Running StatusApp provides an interactive, scrollable table inside any terminal, avoiding a separate web UI.
Code Health & Issues
- MEDIUM – GITHUB_TOKEN permissions –
.github/workflows/black_format.ymllacks explicitpermissions. - MEDIUM – Dependabot missing – No
.github/dependabot.ymlto auto‑updatepyproject.tomldeps. - MEDIUM – Dependency scan absent – No vulnerability‑review step in CI.
- MEDIUM – Checkout persist‑credentials – Token remains in
.git/configfor later steps. - LOW – Job timeouts – Workflow jobs omit
timeout-minutes.
High‑severity findings from static analysis:
- Duplicated code blocks – 1262 six‑line repeats across 292 files (e.g.,
docs/blog/snippets/.../blocking01.py). - Deep nesting – Max indentation depth 8 in core modules (
_compositor.py,_wrap.py,_xterm_parser.py). - Oversized files –
src/textual/app.py(3958 lines) and related modules exceed typical maintainability limits. - File opens without context manager –
docs/blog/images/gen_inspect.py,src/textual/_xterm_parser.py. - Broad exception handling – Numerous examples swallow all exceptions (e.g.,
examples/calculator.py).
All findings are derived from deterministic static analysis; no additional issues were inferred.
The Bottom Line
textual delivers a mature, well‑documented Python UI layer for terminal applications, with a clean modular architecture and extensive test coverage. The primary concerns are code‑size and duplication, which increase change risk in core files like app.py. Teams needing fast‑to‑prototype terminal UIs will benefit, provided they allocate effort for refactoring the identified hotspots and tighten CI security settings.