Framework
Textualize/textual avatar
Textualize/textual

Textual and the second process you need before you can debug your terminal app

The lean application framework for Python. Build sophisticated user interfaces with a simple Python API. Run your apps in the terminal and a web browser.

37,350 stars1,342 forksPythonMIT

At a glance

What is it?
Textual is a MIT licensed Python framework for the same app in a terminal and a browser, and its own packaging tells you what adopting it costs: a separate textual-dev package for the dev console, snapshot tests as the definition of visual correctness, two lockfiles, and an sdist that ships the tests and the offline docs.
Who is it for?
Textual fits a Python team that wants one codebase to serve a terminal UI and a browser UI, that values the testing framework, and that is willing to treat rendered snapshots as reviewable artifacts. It is a poor fit if you need a single-package debug story, if snapshot churn will slow your reviews, or if you expected the browser story to be free and unlimited.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 81 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 27, 2026, and from our analysis. They are not legal advice.

Editorial analysis

textual-dev is a second package and a second terminal

The install line installs two things:

code
pip install textual textual-dev

That second name is the one people miss. The dev console is not a flag you pass to your app, it is a separate package that supplies a console which connects to your running application from another terminal. In it you see system messages and events, and also your own logged messages and your print statements, which is the only way to see ordinary output once your app owns the terminal.

The framework is asynchronous underneath, so your apps can integrate with async libraries, and the README is explicit that if you do not want to use async, Textual will not force it on you. That is the right design decision for a UI toolkit. The cost is elsewhere: a terminal application that paints its own screen has no stdout left for logging, so the debugging loop depends on a second process, a second window, and a package you can forget to install. Without textual-dev, the app runs and you are blind.

textual serve shares one app, Textual Web sells the many

The same application can go on the web. Any Textual app may be served with textual serve, and the README demonstrates it by serving the built-in demo:

code
textual serve "python -m textual"

Then the sentence that matters for anyone planning a deployment: in addition to serving apps locally, you can serve apps with Textual Web, whose firewall-busting technology can serve an unlimited number of applications. The project also notes that since Textual apps have low system requirements, you can install them where Python runs, turning any device into a connected one, with no desktop required.

Read the two routes separately, because they are not the same promise. textual serve is a local sharing tool in the open source package. Serving an unlimited number of applications is the separate product in a different repository, so a plan that assumes free multi tenant hosting is planning around something the pip package does not do. The repository also does not document which interactions behave differently once the app is behind a browser instead of a real terminal.

The offline documentation is the online documentation minus a blog

There are two documentation builds and one navigation file. mkdocs-nav.yml is the source of truth, and the Makefile manufactures a config for each. The online one writes an INHERIT line pointing at mkdocs-online.yml and then appends the whole nav file. The offline one does the same with mkdocs-offline.yml, except it pipes the nav through a filter that drops every entry matching a blog pattern before appending.

That single filter is the entire difference between the two builds, and it is a shell one-liner in a make target rather than a setting in a config file. So adding a new non blog page puts it in both builds, and a blog post is invisible in the offline set by construction. Both generated files are deleted after the build, so nothing tells you afterwards which pages were included.

The offline tree is also packaged: pyproject.toml ships `docs-offline/**` into the source distribution, which means the whole offline documentation set travels to anyone installing from source.

Snapshot tests are how a terminal interface is verified

The Makefile shows the test surface, and it is unusually parallel:

code
$(run) pytest tests/ -n 16 --dist=loadgroup $(ARGS)

Sixteen workers with a loadgroup distribution, a verbose variant, a coverage variant that reports term-missing against the textual package, a typecheck target running mypy over src/textual, and formatting through black over src, with ruff configured separately in pyproject.toml at target-version py39. Alongside them sits a target for updating snapshots, and tests/snapshot_tests/__snapshots__ is excluded from the built package while the tests directory itself is shipped in the source distribution.

For a terminal interface this is the central design decision. Correctness is partly defined by stored images of rendered output, so changing a widget's appearance is a change to test data, and whoever regenerates the snapshots decides what right looks like. The trade is real: a reviewer cannot read that change as a diff, and the .screenshot_cache directory is a local artifact with its own clean target for a reason.

Poetry packages it, uv sits in the tree, and the demo pins 3.12

pyproject.toml is a Poetry project: name textual, version 8.2.8, author Will McGugan, license MIT, python = ^3.9, and classifiers covering Windows 10, Windows 11, MacOS and Linux, with Python 3.9 through 3.14 and a Typing :: Typed marker. So the framework supports a wide range and ships type information, with py.typed listed in the package contents.

The dependency story is split. poetry.lock and uv.lock both sit at the root, and every Makefile target runs through `poetry run`, so a contributor needs Poetry even though a uv lockfile is present. Meanwhile the README offers a no install demo that uses uv directly:

code
python -m textual
uvx --python 3.12 textual-demo

Note the gap: the package supports Python 3.9 and up, the classifiers go to 3.14, and the demo runs on 3.12. The version you develop against, the version you support, and the version the showcase uses are three different numbers.

The sdist ships the tests, the examples and the offline docs

The include list in pyproject.toml is worth reading closely, and so is the comment above it. Beyond src/textual/py.typed, it adds docs/examples to the source distribution, adds tests to the source distribution, and adds `docs-offline/**/*` to the source distribution, while exclude removes the snapshot files. The comment explains why the paths are written the way they are: Poetry populates the exclude list from the content of .gitignore, exclude appears to trump include, and the awkward path specification is the workaround.

So a source install is not a slim package. It carries the test suite, the documentation examples, and the entire offline documentation build, while a wheel carries the library. For most users that is invisible and wasteful; for anyone who inspects what they install, or who builds an air gapped environment, it is a decision somebody made deliberately.

The same comment is also a warning to contributors: edit .gitignore carelessly and the contents of a published distribution can change without any line of pyproject.toml moving.

Active development on a dated cadence, with playful release names

The project is not abandoned. The last push to main is dated 2026-07-11, and the recent releases are v8.2.8 on 2026-06-30, v8.2.7 on 2026-05-19 and v8.2.6 on 2026-05-13, with the version in pyproject.toml reading 8.2.8, matching the newest tag.

What the release list will not tell you is what changed, because the tags carry names rather than descriptions: The more super release, The more Kitty Release, The more selective release. CHANGELOG.md is where the substance lives, and for an application framework the changelog is the document you read before upgrading, not after something breaks.

The repository also carries the governance files a maintained project needs: CONTRIBUTING.md, CODE_OF_CONDUCT.md, an AI_POLICY.md, a .pre-commit-config.yaml, a mypy.ini, a .deepsource.toml, and a .faq/ directory with faq.yml and questions/ that a make target builds with faqtory. That is a well tooled project, and the tooling is also the maintenance surface you inherit.

Editorial conclusion

Textual fits a Python team that wants one codebase to serve a terminal UI and a browser UI, that values the testing framework, and that is willing to treat rendered snapshots as reviewable artifacts. It is a poor fit if you need a single-package debug story, if snapshot churn will slow your reviews, or if you expected the browser story to be free and unlimited. Before you build, install textual-dev as well as textual, read what Textual Web charges for multi application serving, and decide whether your review process can handle image diffs from snapshot updates.

Frequently asked questions

What does "textual" mean?

In this repository textual is a Python framework, not the adjective. It is described as a lean application framework for building cross platform user interfaces with a simple Python API, runnable in the terminal or a web browser, MIT licensed and authored by Will McGugan.

how to install textual

The documented line is pip install textual textual-dev, which pulls the framework and the dev console package together. To look before you install, the project offers python -m textual, and uvx --python 3.12 textual-demo for the separate demo without installing anything.

how to use textual python

You subclass App, yield widgets from compose, and style with a CSS attribute. The README's clock example yields a Digits widget, sets a Screen align rule, and calls set_interval(1, ...) from on_ready, and the fuzzy search command palette opens with ctrl+p.

What is another word for "textual"?

The repository is not a thesaurus, it is the source for the Python framework named textual, version 8.2.8, described in its packaging as a Modern Text User Interface framework with a Development Status of Production/Stable and a Typing :: Typed marker.

what is textual analysis

Nothing here concerns literary analysis. This repository is the Textual Python interface framework, and its subject matter is its widgets, its layout system, its predefined themes, its testing framework, and the examples directory.

Official sources

  1. Official documentation
  2. Official README
  3. Project repository
  4. Release notes
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/textualize-textual.svg)](https://hysenlabs.com/projects/textualize-textual)