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.py defines the App class and the run entry point (line 2296).
  • UI elements are in src/textual/widgets/ and src/textual/css/, while the rendering pipeline is in src/textual/_compositor.py and src/textual/_wrap.py.
  • Documentation, examples and a demo application are shipped under docs/ and examples/, so a new project can be bootstrapped from examples/calculator.py or 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:

  1. runcompose (user‑overridden) → Label, Button, Static, etc. (via compose -> Label x302, compose -> Button x101).
  2. Widgets emit events (post_message) that travel up the DOM (src/textual/dom.py).
  3. The compositor (src/textual/_compositor.py) consumes layout data, calls refresh, 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.yml lacks explicit permissions.
  • MEDIUM – Dependabot missing – No .github/dependabot.yml to auto‑update pyproject.toml deps.
  • MEDIUM – Dependency scan absent – No vulnerability‑review step in CI.
  • MEDIUM – Checkout persist‑credentials – Token remains in .git/config for 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 filessrc/textual/app.py (3958 lines) and related modules exceed typical maintainability limits.
  • File opens without context managerdocs/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.