Textual – How to Add Widgets to a Container

Textual is an excellent Python package for creating beautiful user interfaces in your terminal. By default, Textual will arrange your widgets starting at the top of the screen and appending them in a vertically oriented stack. Each GUI or TUI toolkit provides a way to lay out your widgets. Textual is no different in this respect. They use an object called a container.

You can use containers to create the following types of layouts:

  • Vertical layout
  • Horizontal layout
  • Grid layout
  • and more!

You will be learning how to use all three of these types of layouts. You will also learn how to add more widgets at runtime.

Let’s get started!

Creating a Vertical Layout

The default orientation in Textual is to arrange widgets vertically. You don’t even need to use a CSS file to apply this orientation.

But what does a vertical layout mean anyway? A vertical layout is when you add widgets to your application vertically, from top to bottom. Here is an illustration of what that might look like:

Textual vertical layout illustration

 

Adding widgets to a Textual application will lay out the widgets similarly to the image above. If you want to see that for yourself, then open up your Python editor and create a new file named `vertical.py`.

Then enter the following code into your new script:

# vertical.py

from textual.app import App, ComposeResult
from textual.widgets import Button


class VerticalApp(App):

    def compose(self) -> ComposeResult:
        yield Button("OK")
        yield Button("Cancel")
        yield Button("Go!")


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

Now open up a terminal and run your code. When you do so, you will see three buttons onscreen, with the topmost being your “OK” button and the bottom being the “Go!” button.

Here is a screenshot of the application to give you an idea of what it looks like:

Textual vertical (no CSS)
You can change the widget size, color, and more using each widget’s styles attribute, but using CSS is simpler. Let’s update the code above to use a vertical.tcss file:

# verical_css.py

from textual.app import App, ComposeResult
from textual.widgets import Button


class VerticalApp(App):
    CSS_PATH = "vertical.tcss"

    def compose(self) -> ComposeResult:
        yield Button("OK")
        yield Button("Cancel")
        yield Button("Go!")


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

Now that you are referring to a CSS file, you should go ahead and write one. If you don’t, you will get an error when you attempt to run the code that says the CSS file could not be found.

Go ahead and open your favorite text editor or use your Python editor to create a file named `vertical.tcss`. Then enter the following code:

Screen {
    layout: vertical;
}

Button {
    width: 100%;
    color: yellow;
    background: red;
}

You do not need the Screen portion of the CSS since that is technically taken care of automatically by Textual. Remember, Screen is the default widget when you launch an application. However, it is always good to be explicit so you understand what is happening. If you want the output to look exactly like the previous example, you can delete this CSS’s Button portion and try running the code that way.

If you decide to include the Button portion of the CSS, you will make all of the Button widgets 100% wide, which means they will all stretch across the entire width of the screen. The CSS also defines the button text to be yellow and the buttons themselves to have a read background color.

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

Textual vertical layout with CSS

That’s a fun way to change your vertically oriented widget layout. But what happens if you set the height of the Button widgets to 50%? Well, you have three widgets. Three times 50 will be 150%, which is greater than what can be shown all at once. Textual will add a scrollbar if you add widgets that go off-screen.

Try adding that setting to your CSS and re-run the code. You should see something like the following:

Textual with vertical layout CSS and height at 50%

You should spend a few moments trying out various width and height sizes. Remember, you don’t have to use percentages. You can also use Textual’s other unit types.

Note: All style attributes can be adjusted at runtime, which means you can modify the layout at runtime, too. Use this wisely so as not to confuse the user!

When you finish experimenting, you will be ready to learn how horizontal layouts work!

Horizontal Layout

 

Laying widgets out horizontally, left-to-right, requires a little more work than laying them out vertically. But the change is still pretty minor, and in many ways, it affects only one line in the CSS file.

But before you change the CSS, you will want to update your Python code to point to the new CSS file. Open your Python editor and copy the previous example to a new file. Save it with the same horizontal.py and update the CSS_PATH to point to a new CSS file named horizontal.tcss:

# horizontal.py

from textual.app import App, ComposeResult
from textual.widgets import Button


class HorizontalApp(App):
    CSS_PATH = "horizontal.tcss"

    def compose(self) -> ComposeResult:
        yield Button("OK")
        yield Button("Cancel")
        yield Button("Go!")


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

Yes, this code is almost the same as the previous example, except the CSS_PATH variable. That’s okay. The point is to show you how you can change the layout.

Create your horizontal.tcss file in a Python or text editor to make a horizontally oriented layout. Then enter the following CSS:

Screen {
    layout: horizontal;
}

Button {
    height: 100%;
    color: yellow;
    background: red;
    border: solid green;
}

The CSS above added a border to the buttons to make them stand out a bit more. Depending on the terminal, the widgets appear to blend together more when arranged horizontally. You can add space around the widgets by setting the margin style, though.

When you run this code, you should see something like the following:

Textual horizontal layout with CSS

When using a horizontal layout, the horizontal scrollbar will not automatically appear if the widgets do not fit the screen. If you want to have a horizontal scrollbar, then you will need to set overflow-x: auto;, like in the following CSS:

Screen {
    layout: horizontal;
    overflow-x: auto;
}

Button {
    height: 100%;
    color: yellow;
    background: red;
    border: solid green;
}

Now, set the widgets’ width to greater than 33% so that the scrollbar will appear. Spend some time experimenting, and you’ll soon figure it out!

Layouts with Containers

 

The Textual package has several utility containers you can use to lay out your widgets. You are most likely to use VerticalHorizontal, or Grid containers. You can also combine the containers to create more complex layouts.

Here is a full list of the containers included with Textual at the time of writing:

  • Center
  • Container
  • Horizontal
  • HorizontalScroll
  • Middle
  • ScrollableContainer
  • Vertical
  • VerticalScroll

You will most likely use the CenterMiddleHorizontal, and Vertical containers the most.

Practicing is the best learning method, especially when laying out user interfaces. You can start your container journey by opening your Python editor and creating a new file called horizontal_container.py. Then enter the following code:

# horizontal_container.py

from textual.app import App, ComposeResult
from textual.widgets import Button
from textual.containers import Horizontal


class HorizontalApp(App):

    def compose(self) -> ComposeResult:
        yield Horizontal(
            Button("OK"),
            Button("Cancel"),
            Button("Go!"),
        )


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

You import the Horizontal container from textual.containers. The main contents of a container is its widgets. You reuse the widgets from the previous example here. Pay attention and note that you do not need to use yield inside the container. You can simply add the widget instances instead.

When you run this code, you will see something like this:

Textual horizontal container

What will happen if you use your horizontal.tcss file with this code? Try adding it to the code above and re-run your example.

The result will look familiar:

Textual horizontal container plus CSS

The real benefit using containers comes when you nest them. You’ll find out about that concept next!

Nesting Containers

Nesting containers allows you to combine horizontally and vertically oriented widgets, resulting in rows and columns of widgets. This design pattern can create some pretty nice layouts.

To start, create a new file called nested_containers.py in your Python editor. Then add this code to it:

# nested_containers.py

from textual.app import App, ComposeResult
from textual.widgets import Button
from textual.containers import Horizontal, Vertical


class NestedApp(App):

    def compose(self) -> ComposeResult:
        yield Vertical(
            Horizontal(
                Button("One"),
                Button("Two"),
                classes="row",
            ),
            Horizontal(
                Button("Three"),
                Button("Four"),
                classes="row",
            ),
        )


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

Your code above has a single Vertical container with two Horizontal containers inside. You can think of the Horizontal containers as “rows”. You can see that you set the classes parameters to “row” to identify them. Each row contains two Button widgets.

When you run this code, you will see something like this:

Textual nested containers

This example doesn’t use any CSS. You should do that! Update the code to include a CSS file called nested.tcss, like the code below:

# nested_containers.py

from textual.app import App, ComposeResult
from textual.widgets import Button
from textual.containers import Horizontal, Vertical


class NestedApp(App):
    CSS_PATH = "nested.tcss"

    def compose(self) -> ComposeResult:
        yield Vertical(
            Horizontal(
                Button("One"),
                Button("Two"),
                classes="row",
            ),
            Horizontal(
                Button("Three"),
                Button("Four"),
                classes="row",
            ),
        )


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

Then, create the nested.tcss file. You will be putting the following CSS rules into it:

Button {
    content-align: center middle;
    background: green;
    border: yellow;
    height: 1fr;
    width: 1fr;
}

Here, you set various rules for the Button widgets to follow. You want the buttons to be green with a yellow border. You also set the width and height to 1fr, which causes the buttons to expand to fit all the horizontal and vertical space.

When you run this version of your code, you can see that the user interface has changed significantly:

Textual nested containers

Nice! You should spend some time adjusting the style rules and seeing how to change these layouts.

Wrapping Up

Learning how to create layouts is a fundamental skill that you will need to master to be able to create engaging, intuitive user interfaces. Fortunately, Textual gives you enough tools that you can create your user interfaces fairly easily. No; you don’t get a What-you-see-is-what-you-get (WYSIWYG) tool as you do with some GUI toolkits, such as QT Creator. But you do get live-coding with CSS, and since most of your user interface layouts are controlled there, tweaking the user interface is so nicer.

Want to Learn More Textual?

This tutorial is based on a chapter from my latest book, Creating TUI Applications with Textual and Python.

Creating TUI Applications with Textual and Python

You will learn everything you need to know about Textual from this book. You will also create TEN small applications to apply what you learn. Check it out today!