Textual is a TUI (Text User Interface) framework for Python using Rich as a renderer.



Textual is a TUI (Text User Interface) framework for Python using Rich as a renderer.

The end goal is to be able to rapidly create rich terminal applications that look as good as possible (within the restrictions imposed by a terminal emulator).

Rich TUI will integrate tightly with its parent project, Rich. Any of the existing renderables can be used in a more dynamic application.

This project is currently a work in progress and may not be usable for a while. Follow @willmcgugan for progress updates, or post in Discussions if you have any requests / suggestions.


  • Implement Windows Driver

    Implement Windows Driver

    Trying to run the textual.app example on Windows 10 with Python 3.9.5:

    ➜ python -m textual.app
    Traceback (most recent call last):
     File "C:\Program Files\Python39\lib\runpy.py", line 197, in _run_module_as_main
       return _run_code(code, main_globals, None,
     File "C:\Program Files\Python39\lib\runpy.py", line 87, in _run_code
       exec(code, run_globals)
     File "C:\CodeProjects\Python\Manim\manimvenv\lib\site-packages\textual\app.py", line 19, in <module>
       from .driver import Driver
     File "C:\CodeProjects\Python\Manim\manimvenv\lib\site-packages\textual\driver.py", line 8, in <module>
       import curses
     File "C:\Program Files\Python39\lib\curses\__init__.py", line 13, in <module>
       from _curses import *
    ModuleNotFoundError: No module named '_curses'
    opened by kilacoda-old 29
  • error when running examples

    error when running examples

    i installed textual with pip and i tried to run the first example in the readme with python3 filename.py but i get the error TypeError: on_key() takes 1 positional argument but 2 were given when i press a key. got a similiar error with another example. sorry if im being dumb : p

    opened by itsUrcute 16
  • Add more easing functions

    Add more easing functions

    There are a number of 'easing functions' used by the animation system. At the top of _animator.py you will see the following:

    EASING = {
        "none": lambda x: 1.0,
        "round": lambda x: 0.0 if x < 0.5 else 1.0,
        "linear": lambda x: x,
        "in_cubic": lambda x: x * x * x,
        "in_out_cubic": lambda x: 4 * x * x * x if x < 0.5 else 1 - pow(-2 * x + 2, 3) / 2,
        "out_cubic": lambda x: 1 - pow(1 - x, 3),

    These function have been copied from https://easings.net/ and translated from Javascript to Python. I would like to add the remaining functions to the EASING dict above.

    See the animation.py example as a way of testing the above functions.

    help wanted good first issue 
    opened by willmcgugan 15
  • Wrong CSS styles being applied on hover

    Wrong CSS styles being applied on hover

    I noticed this when adding semantic style variants for Button.

    When I hover over the warning button, the border is being set to tall $primary-lighten-3. I would expect it to be set to tall $warning-lighten-3 given Button.-warning has higher specificity.

    Relevant CSS

        Button {
            border: tall $primary-lighten-3;
        Button.-warning {
            background: $warning;
            color: $text-warning;
            border: tall $warning-lighten-3;  
        Button.-warning :hover {
            background: $warning-darken-1;
            color: $text-warning-darken-1; 
    opened by darrenburns 12
  • Horizontal scrolling

    Horizontal scrolling

    (hope you don't mind me opening an issue to ask a question, I've tried hard to get to the bottom of this myself)

    I'm trying to get horizontal scrolling working with textual and a rich table, but no luck

    Below is what I have so far.

    It seems like the table is refusing to exceed the width of the terminal, thus the horizontal scroll bar of ScrollView is not coming into play.

    Vertical scrolling is working great.

    What am I doing wrong?

    (ref: https://twitter.com/samuel_colvin/status/1426289617632960515)

    from rich.table import Table
    from textual import events
    from textual.app import App
    from textual.widgets import Header, Footer, ScrollView
    class MyApp(App):
        """An example of a very simple Textual App"""
        async def on_load(self, event: events.Load) -> None:
            await self.bind("q", "quit", "Quit")
        async def on_mount(self, event: events.Mount) -> None:
            self.body = body = ScrollView()
            body.virtual_size.width = 300
            await self.view.dock(body)
            async def add_content():
                table = Table(title="Demo", width=300, min_width=300)
                for i in range(40):
                    table.add_column(f'Col {i + 1}', style='magenta')
                for i in range(40):
                    table.add_row(*[f'cell {i},{j}' for j in range(40)])
                await body.update(table)
            await self.call_later(add_content)
    MyApp.run(title="Simple App", log="textual.log")


    python 3.9.5
    OS: Ubuntu 21.04 with standard terminal
    opened by samuelcolvin 9
  • Typing error with Reactive

    Typing error with Reactive

    PyCharm gives me typing error with Reactive, see screenshot:


    I'm unsure why this is the case given that mypy doesn't yield such error.

    Side question: now that I have found myself a project to use Textual while fully understanding that this is WIP, I'll likely come across many little things like this one you may already be aware of. Should I keep opening issues? Or should I use some other, less formal avenue to keep the noise low and only open ticket when you request it? I'm thinking of Twitter DMs, or possibly some discord server and/or DM. Let me know what works best for you at this stage.

    opened by abey79 7
  • Dynamically adding placeholders

    Dynamically adding placeholders

    I was trying to add e.g. a new row to a layout after it was created in on_startup(), but it looks like it has no effect (I tried refresh() and require_layout()). Just asking as I know it might be early for that. Keep up the great work anyway!

    opened by davidbrochart 7
  • [key bindings] Add dynamic

    [key bindings] Add dynamic "$event.[something]" mini-language to our key bindings management

    With this PR we can now use a "mini-language" to inject event-related data into the key bindings' handlers e.g. self.bind("1,2,3", "digit_pressed('$event.key')")

    • My first implementation was using the Python "Format Specification Mini-Language", so the syntax was the following:
       self.bind("1,2,3", "digit_pressed('{event.key}')") # <-- uses curly brackets for interpolation

      But as I was writing some integration tests with various kinds of Python primitives passed to the key bindings handlers I realised that using this syntax was causing an issue: as it's based on curly brackets we could not use Python dicts or sets in the parameters we inject.

    • This is why I ended up opting for a dedicated SDL that doesn't conflict with Python syntax for dicts or set literals. As I don't want to create fancy SDLs out of thin air that users have to learn, I simply based it on the Python Template strings' one.
      So our previous expression becomes this:
       self.bind("1,2,3", "digit_pressed('$event.key')") # <-- uses a "dollar" syntax for interpolation
       # ...which enables this kind of things:
       self.bind("1,2,3", "digit_pressed('$event.key', {'sender_name': 'sidebar'})") # <-- injects a Python dict

    Limits of this "action binding mini-language"

    As explained in one of my comments on the GitHub diff, I also realised that this "action binding mini-language" has some strong limits, which can be a good thing for security but also has limits. We can pass $event.key to the action method for example, but we cannot pass the instance of the Event itself, or the sender instance - because this SDL is based on ast.literal_eval, which intentionally doesn't allow using custom objects.

    I guess that time and dog-fooding will tell us if expressions such as $event.sender (which is the string representation of the sender, rather than the sender instance) is enough, or if we should rather start thinking of something else? :slightly_smiling_face:

    Potential future improvements: dependency injection?

    One potential way to solve this would be to use "a la Pytest" / "a la FastAPI" dependency injection... So if the action method declares a param named event for example we detect that and inject the Event instance for this parameter. Same with a param named sender, etc. :slightly_smiling_face:

    Dependency injection would also allow users to not use error-prone "Python-syntax-in-a-string expressions" - i.e. it's easy to miss the syntax error in something like this:

    self.bind("1,2,3", "digit_pressed('$event.key', {'sender_name: 'sidebar'})")

    (a closing single quote is missing)

    closes #496

    opened by DrBenton 6
  • fix: allow downstream projects to use mypy

    fix: allow downstream projects to use mypy

    Downstream projects get a message error: Skipping analyzing "textual": module is installed, but missing library stubs or py.typed marker when trying to use mypy. This adds the missing marker, copied from rich. tool.poetry.include is not required, though - poetry (now?) picks this up from VCS, like other files.

    Tested with:

    $ pip run build
    $ unzip -l dist/textual-0.1.15-py3-none-any.whl | grep py.typed
            0  01-01-1980 00:00   textual/py.typed
    opened by henryiii 6
  • Add success/warning/error button variants

    Add success/warning/error button variants

    I kept the primary button as is on dark mode. I think I'd prefer if it was the blue/primary background colour on both light and dark mode, but not too fussed either way.

    One kind of annoying thing is the "success" button is the only one where, on hover, the text colour changes.


    opened by darrenburns 5
  • Scroll view and DataTable Widget

    Scroll view and DataTable Widget

    • Moved example to "old examples" directory. We can gradually start porting examples with new API.
    • Copied LRUCache from Rich
    • Implemented ScrollView widget
    • Implemented DataTable widget

    Data Table is still fairly rudimentary in terms of features. You add columns / rows and it will render it.

    A future update will add a caret / selection and other features


    opened by willmcgugan 5
  • Config


    Reading config files and merging them.

    • Things are kept in dictionary form for now as that's how they get merged.
    • We could throw the data into an immutable Pydantic object to ensure validity, but I haven't implemented any validation stuff.
    • Focus here is on reading config files and merging them correctly, essentially pulling all the data together into a single structure which contains the correct merged/prioritised data.
    • Supports multiple config files on a users system. Whether this will be required, I don't know yet.
    • It's the responsibility of the user of this class to pass in the paths to the configuration files. Textual may user appdirs for example to get locations for user config and pass them into this class.
    • Environment variable ignored for now.
    • There's some missing docstrings etc but will add them later.
    • App config will be prefixed with app., not done that yet though.
    opened by darrenburns 0
  • The terminal is empty until I resize it

    The terminal is empty until I resize it

    I used simple code:

    from textual.app import App
    from textual.widgets import Placeholder
    class SimpleApp(App):
        async def on_mount(self) -> None:
            await self.view.dock(Placeholder(), edge="left", size=40)
            await self.view.dock(Placeholder(), Placeholder(), edge="top")

    Then, I ran the code but nothing appeared until i resized terminal


    opened by Stepashka20 2
  • Update docs setup and add button documentation

    Update docs setup and add button documentation

    Added some steps for setup for relevant packages that may not be installed by standard

    Created documentation for the button widget, based on the current main branch (tell me if you prefer documentation to be based off the current code in the css branch)

    opened by HackintoshwithUbuntu 0
  • Documentation about adding other event loops

    Documentation about adding other event loops

    I was trying to view messages from asyncio mqtt updated ASAP with textual and couldn't figure how to add another task in the loop. Then I found this answer which explains how to set it up.

    It might seem obvious for experimented async users but I was puzzled.

    I think this is a key feature for a basic usage of Textual and it should be added in the documentation.

    To be added by @willmcgugan

    opened by qkzk 0
  • Issue running Textual based Theengs Explorer on macOS 10.15.7 Catalina

    Issue running Textual based Theengs Explorer on macOS 10.15.7 Catalina

    Hi all,

    being all new to Textual, but very excited to try out the new Theengs Explorer I'm only ever getting constant loops of

    Task exception was never retrieved
    future: <Task finished name='Task-1458' coro=<TheengsExplorerApp.detection_callback() done, defined at __init__.py:36> exception=LookupError(<ContextVar name='active_app' at 0x113b61130>)>
    Traceback (most recent call last):
      File "__init__.py", line 39, in detection_callback
        await self.scroll_view.update(self.device_table.render(), home=False)
      File "/Users/me/.pyenv/versions/3.8.5/lib/python3.8/site-packages/textual/widgets/_scroll_view.py", line 89, in update
        await self.window.update(renderable)
      File "/Users/me/.pyenv/versions/3.8.5/lib/python3.8/site-packages/textual/views/_window_view.py", line 39, in update
      File "/Users/me/.pyenv/versions/3.8.5/lib/python3.8/site-packages/textual/layouts/vertical.py", line 28, in add
        self._max_widget_width = max(widget.app.measure(widget), self._max_widget_width)
      File "/Users/me/.pyenv/versions/3.8.5/lib/python3.8/site-packages/textual/message_pump.py", line 59, in app
        return active_app.get()
    LookupError: <ContextVar name='active_app' at 0x113b61130>

    while the Textual examples all work fine for me, and all the required dependencies/extra packages for Theengs Explorer are satisfied - TheengsDecoder doesn't exist as an installable package yet, but must be built

    Unless this is an obvious older macOS version issue to you, for which I'd be happy to do some external disk newer macOS testing, any further pointers as to what might cause this would be greatly appreciated.

    The creator of TheengsExplorer, @koenvervloesem, is also following this to chip in.

    Many thanks

    opened by DigiH 1
  • v0.1.18(Apr 30, 2022)

  • v0.1.17(Mar 10, 2022)

  • v0.1.16(Mar 10, 2022)

  • v0.1.15(Jan 31, 2022)

    Windows support has landed. Still experimental -- let us know if you run in to any problems!

    [0.1.15] - 2022-01-31


    • Added Windows Driver
    Source code(tar.gz)
    Source code(zip)
  • v0.1.14(Jan 9, 2022)

  • v0.1.13(Jan 1, 2022)

    [0.1.13] - 2022-01-01


    • Fixed spurious characters when exiting app https://github.com/willmcgugan/textual/issues/82
    • Fixed increasing delay when exiting
    Source code(tar.gz)
    Source code(zip)
  • v0.1.11(Sep 12, 2021)

    [0.1.11] - 2021-09-12


    • Changed message handlers to use prefix handle_
    • Renamed messages to drop the Message suffix
    • Events now bubble by default
    • Refactor of layout


    • Added App.measure
    • Added auto_width to Vertical Layout, WindowView, an ScrollView
    • Added big_table.py example
    • Added easing.py example
    Source code(tar.gz)
    Source code(zip)
  • v0.1.10(Aug 25, 2021)

    [0.1.10] - 2021-08-25


    • Added keyboard control of tree control
    • Added Widget.gutter to calculate space between renderable and outside edge
    • Added margin, padding, and border attributes to Widget


    • Callbacks may be async or non-async.
    • Event handler event argument is optional.
    • Fixed exception in clock example https://github.com/willmcgugan/textual/issues/52
    • Added Message.wait() which waits for a message to be processed
    • Key events are now sent to widgets first, before processing bindings
    Source code(tar.gz)
    Source code(zip)
  • v0.1.3(Jul 5, 2021)

    In this version there is a new more-powerful renderer that supports overlapping regions. There is also a new layout engine which is more intuitive that Rich's Layout clas.

    Source code(tar.gz)
    Source code(zip)
  • v0.1.2(Jun 24, 2021)

Will McGugan
I'm a full-stack software developer, and Python expert. Creator of Rich and @PyFilesystem.
Will McGugan
Color text streams with a polished command line interface

colout(1) -- Color Up Arbitrary Command Output Synopsis colout [-h] [-r RESOURCE] colout [-g] [-c] [-l min,max] [-a] [-t] [-T DIR] [-P DIR] [-d COLORM

nojhan 1.1k Jul 8, 2022
Python composable command line interface toolkit

$ click_ Click is a Python package for creating beautiful command line interfaces in a composable way with as little code as necessary. It's the "Comm

The Pallets Projects 12.6k Jul 6, 2022
Corgy allows you to create a command line interface in Python, without worrying about boilerplate code

corgy Elegant command line parsing for Python. Corgy allows you to create a command line interface in Python, without worrying about boilerplate code.

Jayanth Koushik 6 Feb 15, 2022
A minimal and ridiculously good looking command-line-interface toolkit

Proper CLI Proper CLI is a Python package for creating beautiful, composable, and ridiculously good looking command-line-user-interfaces without havin

Juan-Pablo Scaletti 1 Dec 30, 2021
Simple cross-platform colored terminal text in Python

Colorama Makes ANSI escape character sequences (for producing colored terminal text and cursor positioning) work under MS Windows. PyPI for releases |

Jonathan Hartley 2.9k Jul 5, 2022
Cement is an advanced Application Framework for Python, with a primary focus on CLI

Cement Framework Cement is an advanced Application Framework for Python, with a primary focus on Command Line Interfaces (CLI). Its goal is to introdu

Data Folk Labs, LLC 1.1k Jul 8, 2022
A cross platform package to do curses-like operations, plus higher level APIs and widgets to create text UIs and ASCII art animations

ASCIIMATICS Asciimatics is a package to help people create full-screen text UIs (from interactive forms to ASCII animations) on any platform. It is li

null 3.1k Jul 3, 2022
A fast, stateless http slash commands framework for scale. Built by the Crunchy bot team.

Roid ?? A fast, stateless http slash commands framework for scale. Built by the Crunchy bot team. ?? Installation You can install roid in it's default

Harrison Burt 8 Jan 12, 2022
Python Fire is a library for automatically generating command line interfaces (CLIs) from absolutely any Python object.

Python Fire Python Fire is a library for automatically generating command line interfaces (CLIs) from absolutely any Python object. Python Fire is a s

Google 22.7k Jul 11, 2022
A simple terminal Christmas tree made with Python

Python Christmas Tree A simple CLI Christmas tree made with Python Installation Just clone the repository and run $ python terminal_tree.py More opti

Francisco B. 59 Dec 28, 2021
Library for building powerful interactive command line applications in Python

Python Prompt Toolkit prompt_toolkit is a library for building powerful interactive command line applications in Python. Read the documentation on rea

prompt-toolkit 7.8k Jul 2, 2022
emoji terminal output for Python

Emoji Emoji for Python. This project was inspired by kyokomi. Example The entire set of Emoji codes as defined by the unicode consortium is supported

Taehoon Kim 1.5k Jul 10, 2022
Python and tab completion, better together.

argcomplete - Bash tab completion for argparse Tab complete all the things! Argcomplete provides easy, extensible command line tab completion of argum

Andrey Kislyuk 1.1k Jul 2, 2022
Python library that measures the width of unicode strings rendered to a terminal

Introduction This library is mainly for CLI programs that carefully produce output for Terminals, or make pretend to be an emulator. Problem Statement

Jeff Quast 286 Jul 2, 2022
A thin, practical wrapper around terminal capabilities in Python

Blessings Coding with Blessings looks like this... from blessings import Terminal t = Terminal() print(t.bold('Hi there!')) print(t.bold_red_on_brig

Erik Rose 1.3k Jul 5, 2022
Typer, build great CLIs. Easy to code. Based on Python type hints.

Typer, build great CLIs. Easy to code. Based on Python type hints. Documentation: https://typer.tiangolo.com Source Code: https://github.com/tiangolo/

Sebastián Ramírez 8.2k Jul 7, 2022
Python Command-line Application Tools

Clint: Python Command-line Interface Tools Clint is a module filled with a set of awesome tools for developing commandline applications. C ommand L in

Kenneth Reitz Archive 75 Jun 25, 2022
prompt_toolkit is a library for building powerful interactive command line applications in Python.

Python Prompt Toolkit prompt_toolkit is a library for building powerful interactive command line applications in Python. Read the documentation on rea

prompt-toolkit 7.8k Jul 11, 2022
Terminalcmd - a Python library which can help you to make your own terminal program with high-intellegence instruments

Terminalcmd - a Python library which can help you to make your own terminal program with high-intellegence instruments, that will make your code clear and readable.

Dallas 0 Jun 19, 2022