Textual
A Python framework for terminal apps with a widget tree, CSS-style stylesheets and asyncio event handling, built on the Rich library; the same app can also be served to a web browser.
- What it is
- Terminal user interfaces (TUIs)
- Type
- Open source
- Made by
- Will McGugan and contributors (built at Textualize, Inc. until mid-2025)
- License
- MIT
- Facts checked
- 13 October 2026
What it is
Textual is retained mode, closer to a web page than a drawing API. An app subclasses App and yields widgets from compose(); they form a DOM-like tree under a Screen. Styles live in Textual CSS (.tcss files or a CSS string) with type, id and class selectors and rules such as dock: top; unlike browser CSS, a type selector also matches subclasses. Events and messages are handled by methods named on_<event>, and each widget is powered by its own task on Python’s asyncio, with input arriving as events on the app’s message queue. Reactive attributes refresh a widget automatically when they change, batching several changes into one repaint, and the Worker API moves slow work such as network calls off the message loop so the UI stays responsive.
Under the hood, widgets render to Rich segments (runs of text with one style). A compositor cuts the segments of overlapping widgets into equal-width “chops”, keeps the top-most ones and writes a single stream of escape codes, updating only the regions that changed. A spatial map on a grid of 100 by 20 cells lets it skip widgets scrolled out of view, so finding the visible ones takes about as long for 1,000 widgets as for 8. Apps can take over the screen or run inline below the prompt, and textual-serve runs an app on a server and streams it over a WebSocket to xterm.js in a browser.
Will McGugan, the author of Rich, started Textual in 2021 and founded Textualize, Inc. to work on it full time. In May 2025 the company announced it would wrap up because it hadn’t found a viable business; Textual continues as an MIT-licensed open-source project. It is still maintained, almost entirely by McGugan: version 8.2.8 shipped in June 2026 and commits landed in October 2026.
Why teams pick it
- Web-style development in Python: a widget tree, CSS that restyles built-in and third-party widgets, and live reloading of styles with
textual run my_app.py --dev - Ready-made widgets, from a basic button to a syntax-highlighted
TextArea, with keyboard and mouse input handled for you - Concurrency fits Python’s asyncio: handlers can be
async, and workers keep slow I/O from freezing the interface - One codebase for terminal and browser: through
textual-serve,App.open_urlandApp.deliver_textwork in both
Watch out for
- Its company is gone: since Textualize closed in mid-2025, development depends mostly on one maintainer’s time
- Frequent major versions: 1.0 arrived in December 2024 and 8.0 in February 2026, so upgrades need care
- Needs Python 3.9 or later (3.10 for the tree-sitter syntax highlighting extras), and a terminal with more than 256 colors looks much better; macOS’s default Terminal is limited to 256
- In the browser the app still runs on the server as a subprocess of
textual-serve, with the terminal drawn by xterm.js: it is not a static web app
Alternatives
What all terminal user interfaces (tuis) have in common →Compare side by side →
Bubble TeaOpen sourceA Go framework from Charm for terminal apps built on the Elm Architecture: one model holds the state, Update turns messages into a new model, and View describes the screen.How it fits with other technologies
What Textual uses
A click counter with a header, footer, a key binding and inline CSS · python
from textual.app import App, ComposeResult
from textual.widgets import Button, Footer, Header, Static
class CounterApp(App):
CSS = """
Button { margin: 1 2; }
#label { padding: 1 2; }
"""
BINDINGS = [("q", "quit", "Quit")]
clicks = 0
def compose(self) -> ComposeResult:
yield Header()
yield Static("Clicked 0 times", id="label")
yield Button("Click me", variant="primary")
yield Footer()
def on_button_pressed(self, event: Button.Pressed) -> None:
self.clicks += 1
self.query_one("#label", Static).update(f"Clicked {self.clicks} times")
if __name__ == "__main__":
CounterApp().run() # or .run(inline=True) to draw below the promptSee it in a real stack
No architecture uses Textual yet. Drop it into the playground and wire it up yourself.Open playground →Worth exploring
Where it came from2021–2026
Will McGugan first wrote Rich, a Python library for colored and formatted terminal output, as a hobby project, then asked why not build an application framework on top of it. Textual’s first release, 0.1.0, went to PyPI in June 2021. The terminal offered only the basics (move the cursor, write colored text, read keys and mouse movements), so everything from a button to a text editor had to be built from scratch.
McGugan founded Textualize, Inc. and worked on Textual full time with a small team. By late 2021 the framework took its styling from CSS, separating an app’s layout and design from its logic. Later work added inline apps, serving apps to web browsers, and in December 2024 a 1.0 release.
In May 2025 McGugan announced that Textualize would wrap up within weeks: Textual had been “a solution in search of a problem”, and the team hadn’t found a shared pain point to build a business around. Textual became a community project, still maintained by McGugan, who then built Toad (“Textual Code”), a terminal front end for AI coding agents, on top of it.
Why it’s built this way
- CSS for styling
- Keeping styles out of Python avoids tangled UI code, lets developers restyle built-in and third-party widgets as easily as their own, and makes live editing of the design possible.
- Build on Rich’s segments
- Treating the screen as a 2D array of characters breaks down with double-width characters such as emoji and Asian scripts. Working with Rich’s styled segments, and splitting them into chops, made overlapping widgets tractable.
- asyncio throughout
- Every widget is powered by an async task and handlers can be coroutines, so an app can await network libraries such as httpx, and workers run slow jobs without blocking the UI.
- Serve the terminal protocol to browsers
- Rather than a separate web renderer,
textual-servepipes the app’s escape codes over a WebSocket to xterm.js, with extra APIs for links and file downloads, so one app works in a terminal and a browser.
Timeline
- 2021
Textual 0.1.0 published on PyPI (June); Textual CSS in development (November)
- 2024
Browser features for apps served with textual-serve (September)
- 2024
Textual 1.0 (December)
- 2025
Textualize, the company, announces it will wrap up; Textual continues as open source (May)
- 2025
McGugan releases Toad, built on Textual (December)
- 2026
Textual 8.0 (February); 8.2.8 (June)
- Textual blog: The future of Textualize (May 2025)
- Textual blog: Algorithms for high performance terminal apps (Dec 2024, 1.0)
- Textual blog: Towards Textual Web Applications (Sep 2024)
- Will McGugan: Toad is a unified experience for AI in the terminal (Dec 2025; “my startup for terminals wrapped up mid-2025”)
- Toad pyproject.toml (depends on textual and textual-serve)
- PyPI: textual release history (0.1.0 Jun 2021, 1.0.0 Dec 2024, 8.0.0 Feb 2026, 8.2.8 Jun 2026)
Sources12 · facts checked 13 October 2026
- Textual guide: App Basics (compose, on_ events, asyncio, inline mode)
- Textual guide: Textual CSS (DOM, .tcss, selectors, live editing)
- Textual guide: Reactivity (smart refresh)
- Textual guide: Workers
- Textual docs: Getting started (Python 3.9+, macOS Terminal 256 colors, syntax extras)
- Textual blog: Algorithms for high performance terminal apps (compositor, spatial map)
- Textual blog: Towards Textual Web Applications (textual-serve, xterm.js, open_url, deliver_text)
- Textual blog: The future of Textualize (May 2025)
- Textual pyproject.toml (version 8.2.8, Python ^3.9, tree-sitter extras need 3.10)
- Textual commits on main (maintained in 2026)
- PyPI: textual release history
- Textual LICENSE (MIT, Will McGugan)