Build Terminal UIs with Textual in Python (2026) — full-screen interactive apps in the terminal with widgets, events, and CSS — not just colored print statements.
If you already ship CLIs with Typer/Rich, Textual is the next step: an App with compose(), reactive widgets, and keyboard-friendly layouts.
TL;DR
- Subclass
App, yield widgets fromcompose() - Handle
Button.Pressed/Input.Submittedwithon_*methods - Style with inline
CSSor a CSS file - Run with
MyApp().run()
Install
pip install textual
# or: uv add textual
Example 1 — Input + Button
from textual.app import App, ComposeResult
from textual.widgets import Input, Button, Label
class Greeter(App):
def compose(self) -> ComposeResult:
yield Input(placeholder="Your name", id="name")
yield Button("Greet", id="greet", variant="primary")
yield Label("", id="out")
def on_button_pressed(self, event: Button.Pressed) -> None:
name = self.query_one("#name", Input).value or "friend"
self.query_one("#out", Label).update(f"Hello, {name}!")
if __name__ == "__main__":
Greeter().run()
Example 2 — light CSS
class Greeter(App):
CSS = """
Screen { align: center middle; }
Input { width: 40; margin: 1; }
Button { margin: 1; }
"""
# ... same compose / handlers ...
For streaming backend status into a TUI, pair with FastAPI SSE workers.
Production tips
- Give widgets stable
id=values forquery_one - Keep long work off the UI thread (workers / asyncio)
- Use bindings for power-user shortcuts
- Test headless where possible; snapshot critical screens
Wrap-up
In 2026, operator tools deserve real UIs even when they live in SSH. Textual makes terminal apps feel like small products — compose, events, CSS, done.