Using CSS to Style a Python TUI with Textual

Textual is a Python framework for creating Text Based user interfaces (TUIs). You can create graphical user interfaces in your terminal with Textual.

If you haven’t heard of Textual before, check out An Intro to Textual – Creating Text User Interfaces with Python

In this tutorial, you will learn how to create and style a form. The form won’t do anything, but this tutorial teaches how to add widgets, lay them out, and then give them some style.

Getting Started

If you don’t have Textual yet, you must install it. Textual is not built-in to Python, so you can use pip to get it on your machine.

Open up your terminal and run the following command to install Textual:

python -m pip install textual

Now you’re ready to rock!

Creating a Form in Textual

You are now ready to start coding with Textual. Open up your favorite Python editor and create a new file named form.py.

Then enter the following code:

# form.py

from textual.app import App, ComposeResult
from textual.containers import Center
from textual.screen import Screen
from textual.widgets import Button, Footer, Header, Input, Static


class Form(Static):
    def compose(self) -> ComposeResult:
        """
        Creates the main UI elements
        """
        yield Input(id="first_name", placeholder="First Name")
        yield Input(id="last_name", placeholder="Last Name")
        yield Input(id="address", placeholder="Address")
        yield Input(id="city", placeholder="City")
        yield Input(id="state", placeholder="State")
        yield Input(id="zip_code", placeholder="Zip Code")
        yield Input(id="email", placeholder="email")
        with Center():
            yield Button("Save", id="save_button")


class AddressBookApp(App):
    def compose(self) -> ComposeResult:
        """
        Lays out the main UI elemens plus a header and footer
        """
        yield Header()
        yield Form()
        yield Footer()


if __name__ == "__main__":
    app = AddressBookApp()
    app.run()

Here, you import all the bits and bobs you’ll need to create your form. You can use the Static class to group together multiple widgets. Think of it as a container-widget.

You create the Form() class to contain most of your form’s widgets. You will compose a series of text input widgets where users can fill in their name and address information. There is also a reference to something called Center(), an actual container in Textual that helps you align widgets.

Next, in the AddressBookApp() class, you create a header, the form, and a footer. Now you are ready to run can run your code.

Open up your terminal again and use the following command:

python form.py

When you run your code, you will see something like the following:

Textual form

The default colors work, but you may want to change them to give your application a different look.

You will learn how to do that by using CSS!

CSS Styling

Textual supports a limited subset of CSS that you can use to style your widgets.Create a new file and name it form.css.

Next, add the following code:

Input {
    background: white;
}

Button {
    background: blue;
}

The Input parameter tells Textual to style all the widgets that are of the Input type. In this example, you are setting the background color white.

The Button line item will set all the Button widget’s background color to blue. Of course, in this example, there is only one Button.

Now you need to update your code to tell Textual that you want to load a CSS file:

from textual.app import App, ComposeResult
from textual.containers import Center
from textual.screen import Screen
from textual.widgets import Button, Footer, Header, Input, Static


class Form(Static):
    
    def compose(self) -> ComposeResult:
        """
        Creates the main UI elements
        """
        yield Input(id="first_name", placeholder="First Name")
        yield Input(id="last_name", placeholder="Last Name")
        yield Input(id="address", placeholder="Address")
        yield Input(id="city", placeholder="City")
        yield Input(id="state", placeholder="State")
        yield Input(id="zip_code", placeholder="Zip Code")
        yield Input(id="email", placeholder="email")
        with Center():
            yield Button("Save", id="save_button")


class AddressBookApp(App):
    CSS_PATH = "form.css"
    
    def compose(self) -> ComposeResult:
        """
        Lays out the main UI elemens plus a header and footer
        """
        yield Header()
        yield Form()
        yield Footer()


if __name__ == "__main__":
    app = AddressBookApp()
    app.run()

One-line change is all you need and that change is the first line in your AddressBookApp() class where you set a CSS_PATH variable. You can supply a relative or an absolute path to your CSS file here.

If you want to modify the style of any of the widgets in your TUI, you only need to go into the CSS file.

Try re-running the application and you’ll see an immediate difference:

If you’d like to be more specific about which widgets you want to style, change your CSS to the following:

Input {
    background: white;
}

#first_name {
    background: yellow;
    color: red
}

#address {
    background: green;
}

#save_button {
    background: blue;
}

Here, you leave the Input widgets the same but add some hash-tag items to the CSS. These hash-tagged names must match the id you set for the individual widgets you want to style.

If you specify incorrect id names, those style blocks will be ignored. In this example, you explicitly modify the first_name and address Input widgets. You also call out the save_button Button,. This doesn’t really change the look of the button since you didn’t change the color, but if you add a second Button, it won’t get any special styling.

Here is what it looks like when you run it now:

You may not like these colors, so feel free to try out some of your own. That’s part of the fun of creating a TUI.

Wrapping Up

Now you know the basics of using CSS with your Textual applications. CSS is not my favorite way of applying styling, but this seems to work pretty well with Textual. The other nice thing about Textual is that there is a developer mode that you can enable where you can edit the CSS and watch it change the user interface live.

Give Textual a try and see what you can make!