Description
[](https://discord.gg/Enf6Z3qhVr) [](https://pypi.org/project/textual/) [](https://badge.fury.io/py/textual)   # Textual <img align="right" width="250" alt="clock" src="https://github.com/user-attachments/assets/63e839c3-5b8e-478d-b78e-cf7647eb85e8" /> Build cross-platform user interfaces with a simple Python API. Run your apps in the terminal *or* a web browser. Textual's API combines modern Python with the best of developments from the web world, for a lean app development experience. De-coupled components and an advanced [testing](https://textual.textualize.io/guide/testing/) framework ensure you can maintain your app for the long-term. Want some more examples? See the [examples](https://github.com/Textualize/textual/tree/main/examples) directory. ```python """ An App to show the current time. """ from datetime import datetime from textual.app import App, ComposeResult from textual.widgets import Digits class ClockApp(App): CSS = """ Screen { align: center middle; } Digits { width: auto; } """ def compose(self) -> ComposeResult: yield Digits("") def on_ready(self) -> None: self.update_clock() self.set_interval(1, self.update_clock) def update_clock(self) -> None: clock = datetime.now().time() self.query_one(Digits).update(f"{clock:%T}") if __name__ == "__main__": app = ClockApp() app.run() ``` > [!TIP] > Textual is an asynchronous framework under the hood. Which means you can integrate your apps with async libraries — if you want to. > If you don't want or need to use async, Textual won't force it on you. <img src="https://img.spacergif.org/spacer.gif" width="1" height="64"/> ## Widgets Textual's library of [widgets](https://textual.textualize.io/widget_gallery/) covers everything from buttons, tree controls, data tables, inputs, text areas, and moreβ¦ Combined with a flexible [layout](https://textual.textualize.io/how-to/design-a-layout/) system, you can realize any User Interface you need. Predefined themes ensure your apps will look good out of the box. <table> <tr> <td>  </td> <td>  </td> </tr> <tr> <td>  </td> <td>  </td> </tr> <tr> <td>  </td> <td>  </td> </tr> </table> <img src="https://img.spacergif.org/spacer.gif" width="1" height="32"/> ## Installing Install Textual via pip: ``` pip install textual textual-dev ``` See [getting started](https://textual.textualize.io/getting_started/) for details. <img src="https://img.spacergif.org/spacer.gif" width="1" height="32"/> ## Demo Run the following command to see a little of what Textual can do: ``` python -m textual ``` Or try the [textual demo](https://github.com/textualize/textual-demo) *without* installing (requires [uv](https://docs.astral.sh/uv/)): ```bash uvx --python 3.12 textual-demo ``` <img src="https://img.spacergif.org/spacer.gif" width="1" height="32"/> ## Dev Console <img align="right" width="40%" alt="devtools" src="https://github.com/user-attachments/assets/12c60d65-e342-4b2f-9372-bae0459a7552" /> How do you debug an app in the terminal that is also running in the terminal? The `textual-dev` package supplies a dev console that connects to your application from another terminal. In addition to system messages and events, your logged messages and print statements will appear in the dev console. See [the guide](https://textual.textualize.io/guide/devtools/) for other helpful tools provided by the `textual-dev` package. <img src="https://img.spacergif.org/spacer.gif" width="1" height="32"/> ## Command Palette Textual apps have a *fuzzy search* command palette. Hit `ctrl+p` to open the command palette. It is easy to extend the command palette with [custom commands](https://textual.textualize.io/guide/command_palette/) for your application.  <img src="https://img.spacergif.org/spacer.gif" width="1" height="32"/> # Textual β€οΈ W
Release History
| Version | Changes | Urgency | Date |
|---|---|---|---|
| 8.2.4 | Imported from PyPI (8.2.4) | Low | 4/21/2026 |
| v8.2.4 | Small potatoes update, to fix a glitch with anchor. ## [8.2.4] - 2026-04-19 ### Added - Added `DOM.update_classes` https://github.com/Textualize/textual/pull/6478 ### Fixed - Fixed anchor released when scrolling down with the trackpad https://github.com/Textualize/textual/pull/6503 | High | 4/19/2026 |
| v8.2.3 | ## [8.2.3] - 2026-04-05 ### Changed - Reduce lag when resizing window, by moving resize from idle to a timer https://github.com/Textualize/textual/pull/6471 | Medium | 4/5/2026 |
| v8.2.2 | Fixed an issue where styles were being unneccesarily updated when resizing. Textual apps will now adapt to changes in the terminal size much more quickly. ## [8.2.2] - 2026-04-03 ### Fixed - Fixed Pointless style updates when resizing https://github.com/Textualize/textual/pull/6464 | Medium | 4/3/2026 |
| v8.2.1 | Fixes a crash when a selected widget is removed while selecting ## [8.2.1] - 2026-03-29 ### Fixed - Fix crash when a widget disapears between selections https://github.com/Textualize/textual/pull/6455 | Medium | 3/29/2026 |
| v8.2.0 | This release enhances text selection, with auto-scrolling, and the ability to select across container widgets. This work was sponsored by Mistral AI. ## [8.2.0] - 2026-03-27 ### Added - Auto-scrolling on select https://github.com/Textualize/textual/pull/6440 - Selecting over containers https://github.com/Textualize/textual/pull/6440 - Added `App.ENABLE_SELECT_AUTO_SCROLL`, `App.SELECT_AUTO_SCROLL_LINES`, `App.SELECT_AUTO_SCROLL_SPEED` to tweak auto scrolling behavior https://githu | Medium | 3/27/2026 |
| v8.1.1 | ## [8.1.1] - 2026-03-10 ### Fixed - Hotfix for animation on complete https://github.com/Textualize/textual/pull/6412 | Low | 3/10/2026 |
| v8.1.0 | This release should smooth scrolling large documents, particularly for Python < 3.14 ## [8.1.0] - 2026-03-10 ### Changed - Replace circuar references in DOM with weak references to improve GC times https://github.com/Textualize/textual/pull/6410 - When animating an attribute a second time, the original `on_complete` is now called https://github.com/Textualize/textual/pull/6410 ### Added - Added experimental `App.PAUSE_GC_ON_SCROLL_` boolean (disabled by default) https://github.co | Low | 3/10/2026 |
| v8.0.2 | ## [8.0.2] - 2026-03-03 ### Changed - Themes are now in alphabetical order in command palette https://github.com/Textualize/textual/pull/6405 ### Fixed - Fixed issues with Directory Tree https://github.com/Textualize/textual/pull/6405 | Low | 3/3/2026 |
| v8.0.1 | Small update ## [8.0.1] - 2026-03-01 ### Fixed - `DirectoryTree` runs more operations in a thread to avoid micro-freezes ### Changes - Some tweaks to garbage collection to reduce gc time https://github.com/Textualize/textual/pull/6402 | Low | 3/1/2026 |
| v8.0.0 | The major version change is due to changing `Select.BLANK` to `Select.NULL`, to avoid an unfortunate name clash. See below for the full changes... ## [8.0.0] - 2026-02-16 ### Added - Added `mode` argument to `push_screen` and `push_screen_wait` to enable pushing a screen to a non-active mode https://github.com/Textualize/textual/pull/6362 - Added `App.mode_change_signal` and `App.screen_change_signal` https://github.com/Textualize/textual/pull/6362 - Added `Tabs.get_tab` https://gi | Low | 2/16/2026 |
| v7.5.0 | The DataTable row cursor will now extend the full width of the datatable, which looks better. The DataTable will now only emit a `*Selected` message if clicking a second time. ### [7.5.0] - 2026-01-30 - The DataTable row cursor will extend to the full width if there is excess space https://github.com/Textualize/textual/pull/6345 - The DataTable will send a selected event on click, only if the cell / row / column is currently highlighted https://github.com/Textualize/textual/pull/6345 | Low | 1/30/2026 |
| v7.4.0 | Adds a `pointer` rule to TCSS, so you can change how the mouse pointer looks over a given widget. https://github.com/user-attachments/assets/9981cfe1-27c8-4586-9eb1-4f513d3d0764 See https://textual.textualize.io/styles/pointer/ for details ## [7.4.0] - 2026-01-25 ### Added - Added `pointer` rule https://github.com/Textualize/textual/pull/6339 | Low | 1/25/2026 |
| v7.3.0 | A few fixes and small features. Enjoy. ## [7.3.0] - 2026-01-15 ### Fixed - Fixed triple click on command palette raising an exception https://github.com/Textualize/textual/pull/6329 ### Added - Added `DOM.query_one_optional` - Added `default` parameter to `get_component_rich_style` get_component_rich_style ### Changed - Added super+c (command on mac) alternative bindings for copy, for terminals that support it (Ghostty does) - Allow `Sparkline` to be of any height, not jus | Low | 1/15/2026 |
| v7.2.0 | Small update to the help system. The help panel will look for a HELP attribute on the focused widget, but now it will also look at ancestors until it finds a usable HELP attribute. There are also a few changes to add more titles to the keys area. ## [7.2.0] - 2026-01-11 ### Changed - The help panel will look at ancestor widgets for a `HELP` attribute if there isn't one on the focused widget https://github.com/Textualize/textual/pull/6320 | Low | 1/11/2026 |
| v7.1.0 | ## [7.1.0] - 2026-01-10 ### Fixed - Fixed issue with missing refresh https://github.com/Textualize/textual/pull/6318 ### Added - Added Widget.BLANK which can optimize rendering of large widgets (typically containers that scroll) https://github.com/Textualize/textual/pull/6318 | Low | 1/10/2026 |
| v7.0.3 | ## [7.0.3] - 2026-01-09 ### Fixed - Fixed performance issue with large scrollable containers https://github.com/Textualize/textual/pull/6317 | Low | 1/9/2026 |
| v7.0.2 | ## [7.0.2] - 2026-01-09 ### Fixed - Removed superfluous style updates when setting `display` attribute. https://github.com/Textualize/textual/pull/6316 | Low | 1/9/2026 |
| v7.0.1 | A small optimization to speed up transition when popping a screen. ## [7.0.1] - 2026-01-07 ### Added - Added a `refresh_styles` boolean to the `ScreenResult` message which reduces style updates when popping screens | Low | 1/7/2026 |
| v7.0.0 | This is a much smaller change than the version number may suggest. A breaking change to a method added just a few days ago. But Semver mandates the major version bump. This release has two new themes, thanks to @NSPC911 ## [7.0.0] - 2026-01-03 ### Changed - `Node.update_node_styles` has grown a `animate` parameter ### Added - Adde atom-one-dark and atom-one-light themes @NSPC911 https://github.com/Textualize/textual/pull/6301 | Low | 1/3/2026 |
| v6.12.0 | A small update to address a performance issue. Previously if you dismiss a screen and the base screen has a lot of widgets, you would could get a noticeable pause (anything up to half a second). With a reasonable number of widgets you would probably not notice. But this update fixes that. ## [6.12.0] - 2025-01-02 ### Fixed - Fixed unnecessary style update when popping screens, which may have caused noticable pauses changing screens (with a lot of widgets) https://github.com/Textualize/t | Low | 1/2/2026 |
| v6.11.0 | A very small updated require for the Toad project. ## [6.11.0] - 2025-12-18 ### Added - Added a `TextSelected` event. https://github.com/Textualize/textual/pull/6290 | Low | 12/18/2025 |
| v6.10.0 | Mainly a fix for some new themes, but also an update to toggle buttons. If you have toggle buttons in your app, this may impact snapshot tests. ## [6.10.0] - 2025-12-16 ### Fixed - Fixed broken themes https://github.com/Textualize/textual/pull/6286 - Updated toggle button style for consistency https://github.com/Textualize/textual/pull/6286 | Low | 12/16/2025 |
| v6.9.0 | Very small release. Mostly for the awesome themes that were recently contributed. ## [6.9.0] - 2025-12-14 ### Added - Added Solarized Dark theme https://github.com/Textualize/textual/pull/6278 - Added RosΓ© Pine themes https://github.com/Textualize/textual/pull/6277 ### Fixed - Fixed fuzzy matcher displaying wrong matched characters with simple substring match https://github.com/Textualize/textual/pull/6282 | Low | 12/14/2025 |
| v6.8.0 | A few fixes, and a very minor feature... ## [6.8.0] - 2025-12-07 ### Added - Added `Content.blank` https://github.com/Textualize/textual/pull/6264 ### Fixed - Fixed `Input` cursor color display in ANSI mode (`ansi_color=True`) https://github.com/Textualize/textual/issues/6234 - Fixed alt modifier on systems without extended Key Protocol https://github.com/Textualize/textual/pull/6267 - Fixed an issue where alpha keys with modifiers weren't lower cased. If you have bound to somet | Low | 12/7/2025 |
| v6.7.1 | Hotfix for `Content.fold` from last release. ## [6.7.1] - 2025-12-1 ### Fixed - Fixed `Content.fold` https://github.com/Textualize/textual/pull/6256 | Low | 12/1/2025 |
| v6.7.0 | ## [6.7.0] - 2025-11-29 ### Added - Added `GridLayout.max_column_width` https://github.com/Textualize/textual/pull/6228 - Added `Content.fold` https://github.com/Textualize/textual/pull/6238 - Added `strip_control_codes` to Content constructors https://github.com/Textualize/textual/pull/6238 ### Changed - Added `Screen.get_loading_widget` which deferes to `App.get_loading_widget` https://github.com/Textualize/textual/pull/6228 ### Fixed - Fixed `anchor` with `ScrollView` wi | Low | 11/29/2025 |
| v6.6.0 | A few minor updates and fixes. Also a style change for the checkbox widget. Expect snapshot test files if you have used checkboxes. Thanks to the contributors! ## [6.6.0] - 2025-11-10 ### Fixed - Fixed `TextArea` cursor display on wrapped lines https://github.com/Textualize/textual/pull/6196 - Fixed `remove_children` not refreshing layout https://github.com/Textualize/textual/pull/6206 - Fixed flicker with :hover pseudo class https://github.com/Textualize/textual/pull/6214 - Fi | Low | 11/10/2025 |
| v6.5.0 | A small release; one fix, one bug. Mainly so I could release on Halloween. π¦ ## [6.5.0] - 2025-10-31 ### Added - Added `DOMNode.trap_focus` https://github.com/Textualize/textual/pull/6202 ### Fixed - Fixed issue with focus + scroll https://github.com/Textualize/textual/pull/6203 | Low | 10/31/2025 |
| v6.4.0 | Some fixes and a change to the command palette to use shorter commands, which look better in a list and are more memorable. There is also a optimization which you may notice if you have complex widgets. ## [6.4.0] - 2025-10-22 ### Fixed - Fixed type hint aliasing for App under TYPE_CHECKING https://github.com/Textualize/textual/pull/6152 - Fixed circular dependency effecting `bazel` users https://github.com/Textualize/textual/pull/6163 - Fixed for text selection with double width c | Low | 10/22/2025 |
| v6.3.0 | Version 6.3.0 adds support for Python 3.14, but drops support for Python3.8. If you are updating, you may also want to update `textual-dev`. There is also a new CSS rule, and a fix for code highlighting. Enjoy! ## [6.3.0] - 2025-10-11 ### Added - Added scrollbar-visibility rule https://github.com/Textualize/textual/pull/6156 ### Fixed - Fixed highlight not auto-detecting lexer https://github.com/Textualize/textual/pull/6167 ### Changed - Dropped support for Python3. | Low | 10/11/2025 |
| v6.2.1 | Hot fix for 2 copy related issues ## [6.2.1] - 2025-10-01 - Fix inability to copy text outside of an input/textarea when it was focused https://github.com/Textualize/textual/pull/6148 - Fix issue when copying text after a double click https://github.com/Textualize/textual/pull/6148 | Low | 10/1/2025 |
| v6.2.0 | A mixed back of features and fixes. See the changelog for details! ## [6.2.0] - 2025-09-30 ### Changed - Eager tasks are now enabled On Python3.12 and above https://github.com/Textualize/textual/pull/6102 - `Widget._arrange` is now public (as `Widget.arrange`) https://github.com/Textualize/textual/pull/6108 - Reduced number of layout operations required to update the screen https://github.com/Textualize/textual/pull/6108 - The :hover pseudo-class no applies to the first widget und | Low | 9/30/2025 |
| v6.1.0 | In this release we have a new "block" border style, and new flat style buttons. https://github.com/user-attachments/assets/c35ab5eb-8d91-43a6-8135-9e7f8f21344f ## [6.1.0] - 2025-08-01 ### Added - Added `Button.flat` boolean to enable flat button style https://github.com/Textualize/textual/pull/6094 - Added `namespaces` parameter to `run_action` https://github.com/Textualize/textual/pull/6094 - Added "block" border style https://github.com/Textualize/textual/pull/6094 | Low | 9/2/2025 |
| v6.0.0 | This is a fairly large update, with some new features and optimizations. There are a few breaking changes, which are unlikely to impact many apps. Unless you have build custom line-API widgets. See below for the details. You may have to regenerate your snapshot tests, mostly as a result of the optimizations. I wouldn't expect the changes result in any material changes. Thanks to all contributors! # [6.0.0] - 2025-08-31 ### Fixed - Fix type hint for SelectType: only hashable ty | Low | 8/31/2025 |
| v5.3.0 | A fix for Markdown IDs, a method to optimize Content, and an addition to reactives to initialize from a method. Release notes below: # [5.3.0] - 2025-08-07 ### Added - Added `Content.simplify` https://github.com/Textualize/textual/pull/6023 - Added `textual.reactive.Initialize` https://github.com/Textualize/textual/pull/6023 ### Fixed - Fixed issue with IDs in markdown https://github.com/Textualize/textual/pull/6019 https://github.com/Textualize/textual/pull/6023 | Low | 8/7/2025 |
| v5.2.0 | This release adds a new "stream" layout. It's a little experimental at the moment, and undocumented. For the brave only! ## [5.2.0] - 2025-08-01 ### Added - Added a 'stream' layout, which is a lot like vertical but with fewer supported rules (which is why it is faster), will remain undocumented for now. https://github.com/Textualize/textual/pull/6013 | Low | 8/1/2025 |
| v5.1.1 | For some reason I still don't understand, poetry included pycache files in the last PyPi release. After updating Poetry, the build is a more sensible size. There are no code changes in this release. | Low | 7/31/2025 |
| v5.1.0 | This release adds an `:empty` pseudo-class which matches widgets with no children. You could use this to hide a container that doesn't have any children, for example: ```css .container:empty { display: none; } ``` Also in this release, support for scrolling left and right via the trackpad or a mouse that supports it. This was a contribution from @fancidev Full changes below: ## [5.1.0] - 2025-07-31 ### Added - Added `empty` pseudo class, which applies when a widget has | Low | 7/31/2025 |
| v5.0.1 | A hotfix. See below for details. ## [5.0.1] - 2025-07-25 ### Fixed - Fixed appending to Markdown widgets that were constructed with an existing document https://github.com/Textualize/textual/pull/5990 | Low | 7/25/2025 |
| v5.0.0 | This is quite a large release! Fueled in part by my work on [Toad](https://willmcgugan.github.io/announcing-toad/) Markdown rendering has been improved, with full text selection, prettier code blocks and tables. Plus streaming support. <img width="1626" height="1310" alt="Screenshot 2025-07-25 at 08 37 30" src="https://github.com/user-attachments/assets/896886c1-d960-499a-a455-6e992231ee1c" /> Plenty of other fixes and additions. Thats to everyone who contributed code and issues! The | Low | 7/25/2025 |
| v4.0.0 | The highlight of this release is the new [Markdown.append](https://textual.textualize.io/widgets/markdown/#textual.widgets.Markdown.append) method which can be used to efficiently stream markdown content (like you might get from an LLM). https://github.com/user-attachments/assets/57fbb0de-bbda-4903-ae09-1b2bd18afe96 The [Widget.anchor](https://textual.textualize.io/api/widget/#textual.widget.Widget.anchor) method has changed semantics (and also works much better), which is the reason for t | Low | 7/12/2025 |
| v3.7.1 | A hotfix for text selection with soft wrapping in the TextArea widget ## [3.7.1] - 2025-07-09 ### Fixed - Fixed broken text selection with soft_wrap=False https://github.com/Textualize/textual/pull/5940 | Low | 7/9/2025 |
| v3.7.0 | A few enhancements, including a handy [getters](https://textual.textualize.io/api/getters/) module for creating properties to get widgets. Also a potentially breaking change, see below for the details... ## [3.7.0] - 2025-07-07 ### Added - Added textual.getters https://github.com/Textualize/textual/pull/5930 - Added a `show_cursor` boolean to TextArea https://github.com/Textualize/textual/pull/5934 ### Changed - Potential breaking change: Changed default `query_one` and `query | Low | 7/7/2025 |
| v3.6.0 | Some substantial optimizations and tweaks for the TextArea widget, and the usual fixes. ## [3.6.0] - 2025-07-06 ### Fixed - Fixed issue with the "transparent" CSS value not being transparent when set using python https://github.com/Textualize/textual/pull/5890 - Fixed issue with pushing screens when Input has mouse captured https://github.com/Textualize/textual/pull/5900 - Implemented workaround for Ghostty bug which produces negative mouse coordinates https://github.com/Textualize/te | Low | 7/6/2025 |
| v3.5.0 | This release contains some optimizations to startup time, which may be significant if you create a lot of widgets. There are also some visual updates to Markdown. This release will break your snapshots, although I don't expect any visual changes. If you are using the snapshot plugin, you will need to regenerate those snapshots. Even if you give them a quick scan, this should only take a few minutes. ## [3.5.0] - 2025-06-20 ### Changed - Optimized startup https://github.com/Textualize | Low | 6/20/2025 |
| v3.4.0 | Mostly fixes, although there is a notable change to markup. Previously anything in square brackets was considered a tag, which resulted in markup errors with Python list literals. i.e. `[1,2,3]` would be interpreted as a tag. The Content markup parser has been made more lenient in these cases, and treats them as literal text. ## [3.4.0] - 2025-06-14 ### Fixed - Fixed issues with initial flicker in `TextArea` rendering https://github.com/Textualize/textual/issues/5841vcomm - Fixed is | Low | 6/14/2025 |
| v3.3.0 | The first community supported release. Mostly fixes and a few helpful additions. See below for details... ## [3.3.0] - 2025-06-01 ### Fixed - Fixed `VERTICAL_BREAKPOINTS` doesn't work https://github.com/Textualize/textual/pull/5785 - Fixed `Button` allowing text selection https://github.com/Textualize/textual/pull/5770 - Fixed running `App.run` after `asyncio.run` https://github.com/Textualize/textual/pull/5799 - Fixed triggering a deprecation warning in py >= 3.10 https://github. | Low | 6/1/2025 |
| v3.2.0 | There are a few interesting features in this release. Many widgets have grown a `compact` reactive. If you set this to `True` then the widget will have a compact (borderless) style. Reactives have a new `toggle_class` attribute, that toggles a TCSS classname according to the truthyness of its value. If that sounds complicated, it really isn't in practice. Let's see it in action: ```python class MyWidget(Widget): compact = reactive(False, toggle_class="-textual-compact") ``` Th | Low | 5/2/2025 |
| v3.1.1 | ## [3.1.1] - 2025-04-22 ### Fixed - Fixed issue with tint filter https://github.com/Textualize/textual/pull/5757 - Fixed a crash when setting keymap before app mount https://github.com/Textualize/textual/issues/5742 | Low | 4/22/2025 |
| v3.1.0 | Mostly fixes, some API enhancements. See below. ## [3.1.0] - 2025-04-12 ### Fixed - Fixed markup escaping edge cases https://github.com/Textualize/textual/pull/5697 - Fixed incorrect auto height in Collapsible https://github.com/Textualize/textual/pull/5703 - Fixed issue with keymaps and single-letter keys https://github.com/Textualize/textual/pull/5726 - Fixed `OptionList` size after removing or clearing options https://github.com/Textualize/textual/issues/5728 - Fixed footer / key | Low | 4/12/2025 |
