textual-tui
Use this skill when the task is fundamentally about building or changing a Textual app, not merely printing Rich output or writing a non-interactive CLI.
Start by classifying the app
Pick the closest shape before writing code:
-
Single-screen shell
One main view with panels, tables, forms, or logs. Prefer containers plus built-in widgets. -
Multi-screen workflow
Large context changes, separate flows, or drill-down views. PreferScreen/ModalScreen. -
Multi-mode admin app
Persistent top-level areas such as “dashboard / jobs / settings / logs”. Prefer namedMODES, screen stacks, and command palette support. -
Data explorer
Records plus details, filters, or side panes. PreferDataTable, details panel, responsive breakpoints, and keyboard navigation. -
Document or filesystem tool
PreferDirectoryTree,MarkdownViewer,TextArea,Tree, and delivery APIs for export/download. -
Chat / streaming / long-running task UI
Prefer a scrollable transcript or log plus@work/ workers for background operations.
If the user has not chosen an architecture, choose one and proceed.
Default engineering stance
- Prefer built-in widgets first. Only hand-roll behaviour when a built-in widget clearly does not fit.
- Keep the
Appthin. Move screen-specific logic intoScreenclasses and reusable composite widgets. - Prefer
.tcssfiles over inlineCSSonce styling grows beyond a toy example. - Use IDs and semantic classes deliberately so styling and Pilot tests stay stable.
- Design for narrow terminals first, then add split panes and breakpoint-driven layouts.
- Leave behind tests whenever behaviour changes.
Choose the right Textual primitive
- Use
Screenwhen navigation changes the user’s working context. - Use
ModalScreenfor short interruptions: confirmations, pickers, destructive actions. - Use
ContentSwitcherfor wizard steps or one-screen subflows. - Use named
MODESwhen the app has durable top-level areas with separate navigation stacks. - Use command palette providers when there are many actions, bindings, or discoverability matters.
- Use workers for network, subprocess, parsing, search, sleeps, or anything that may block input.
See:
Widget-first selection rules
Before inventing custom widgets, check the widget atlas.
Common defaults:
DataTablefor record-heavy viewsDirectoryTreefor filesystem navigationMarkdownViewerfor rich document viewsTextAreafor editingTabbedContentfor grouped settings or alternate panesLog/RichLogfor live outputSelectionList,OptionList,ListView,Tree,Select,Switch,Input,Buttonfor most interaction needs
Reactivity and workers
Use the playbook in reactivity and workers.
Core rules:
- Put fast derived state in
compute_*, but keep it cheap and side-effect free. - Use
watch_*for UI reactions, not blocking work. - Use
varwhen you want state without automatic refresh machinery. - Use
set_reactivebefore mount when initial state changes should not trip watchers early. - Move blocking work into
@workorrun_worker(...). - Use
exclusive=Truefor stale-search cancellation and similar “latest request wins” flows. - For thread workers, update the UI via messages or
call_from_thread.
Browser, dev loop, and delivery
Textual may run in a terminal or be served to a browser. Build with both in mind when relevant.
- Use
textual run --devwhile iterating. - Use
textual consoleand devtools when behaviour is unclear. - Use
textual servewhen browser parity matters. - Prefer
deliver_text,deliver_binary, ordeliver_screenshotfor browser-friendly exports and downloads. - Use
open_urlwhen handing off to the user’s browser is appropriate.
See:
Testing is part of the feature
Default output after any non-trivial change:
- one smoke test with
run_test() - one behaviour test for the changed flow
- one narrow-terminal or alternate-size test when layout matters
- one snapshot test when the view structure matters visually
See testing matrix.
When working on an existing project
Start with the scripts, then refine by hand:
python scripts/inspect_textual_project.py <project>python scripts/audit_textual_project.py <project>- Generate scaffolds or tests only after you understand the existing structure.
Use the audit to catch:
- oversized
Appclasses - blocking handlers
- missing breakpoints
- missed built-in widget opportunities
- missing command palette or delivery APIs
- missing Pilot tests
Bundled scripts
-
scripts/scaffold_textual_app.py
Generate starter apps, TCSS, tests, optionalpyproject.toml, and CI workflow. -
scripts/inspect_textual_project.py
Inventory app classes, screens, widgets, bindings, IDs, workers, and styling. -
scripts/audit_textual_project.py
Heuristic architecture/performance/test audit for an existing Textual project. -
scripts/generate_pilot_tests.py
Emit starter smoke and behaviour tests for an existing app. -
scripts/dump_dom_and_bindings.py
If Textual is installed, launch an app underrun_test()and dump DOM and active bindings. -
scripts/emit_textual_pyproject.py
Generate a packageable Hatch-basedpyproject.toml. -
scripts/emit_github_actions_ci.py
Generate a GitHub Actions workflow for Textual tests. -
scripts/build_upstream_pattern_atlas.py
Summarise a local Textual repo snapshot intoreferences/repo-map.mdandreferences/upstream-pattern-atlas.md. -
scripts/self_check.py
Compile scripts and scaffold all bundled templates as a package validation step.
Bundled starter templates
Available scaffolds:
dashboardformchatdata-explorerfile-browsersettingswizardlog-monitoreditoradmin-modesdownload-demo
List them with:
python scripts/scaffold_textual_app.py --list-templates
Generate one with:
python scripts/scaffold_textual_app.py \
--template data-explorer \
--module my_app \
--class-name MyApp \
--app-title "My App" \
--output-dir .
Output checklist
Before you finish, aim to leave behind:
- a clear app structure
- stable IDs/classes for styling and tests
- TCSS separated from Python unless the app is tiny
- background work off the main event path
- keyboard-discoverable actions
- responsive layout decisions
- at least a smoke test and one behaviour test
- notes on how to run the app in dev mode