Skip to content

Save time with Textual containers

Textual's containers provide a convenient way of arranging your widgets. Let's look at them in a little detail.

Are you in the right place?

We are talking about Textual container widgets here. Not to be confused with containerization—which is something else entirely!

What are containers?

Containers are reusable compound widgets with preset styles to arrange their children. For instance, there is a Horizontal container which arranges all of its children in a horizontal row. Let's look at a quick example of that:

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


class Box(Placeholder):
    """Example widget."""

    DEFAULT_CSS = """
    Box {
        width: 16;
        height: 8;        
    }
    """


class ContainerApp(App):
    """Simple app to play with containers."""

    def compose(self) -> ComposeResult:
        with Horizontal():  # (1)!
            yield Box()  # (2)!
            yield Box()
            yield Box()


if __name__ == "__main__":
    app = ContainerApp()
    app.run()
  1. Use the with statement to add the Horizontal container.
  2. Any widgets yielded within the Horizontal block will be arranged in a horizontal row.

Here's the output:

ContainerApp PlaceholderPlaceholderPlaceholder

Note that inside the Horizontal block new widgets will be placed to the right of previous widgets, forming a row. This will still be the case if you later add or remove widgets. Without the container, the widgets would be stacked vertically.

How are containers implemented?

Before I describe some of the other containers, I would like to show how containers are implemented. The following is the actual source of the Horizontal widget:

class Horizontal(Widget):
    """An expanding container with horizontal layout and no scrollbars."""

    DEFAULT_CSS = """
    Horizontal {
        width: 1fr;
        height: 1fr;
        layout: horizontal;
        overflow: hidden hidden;
    }
    """

That's it! A simple widget with a few preset styles. The other containers are just as simple.

Horizontal and Vertical

We've seen the Horizontal container in action. The Vertical container, as you may have guessed, work the same but arranges its children vertically, i.e. from top to bottom.

You can probably imagine what this looks like, but for sake of completeness, here is an example with a Vertical container:

from textual.app import App, ComposeResult
from textual.containers import Vertical
from textual.widgets import Placeholder


class Box(Placeholder):
    """Example widget."""

    DEFAULT_CSS = """
    Box {
        width: 16;
        height: 8;        
    }
    """


class ContainerApp(App):
    """Simple app to play with containers."""

    def compose(self) -> ComposeResult:
        with Vertical():  # (1)!
            yield Box()
            yield Box()
            yield Box()


if __name__ == "__main__":
    app = ContainerApp()
    app.run()
  1. Stack the widgets vertically.

And here's the output:

ContainerApp Placeholder Placeholder Placeholder

Three boxes, vertically stacked.

Styling layout

You can set the layout of a compound widget with the layout rule.

Size behavior

Something to keep in mind when using Horizontal or Vertical is that they will consume the remaining space in the screen. Let's look at an example to illustrate that.

The following code adds a with-border style which draws a green border around the container. This will help us visualize the dimensions of the container.

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


class Box(Placeholder):
    """Example widget."""

    DEFAULT_CSS = """
    Box {
        width: 16;
        height: 8;        
    }
    """


class ContainerApp(App):
    """Simple app to play with containers."""

    CSS = """
    .with-border {
        border: heavy green;
    }
    """

    def compose(self) -> ComposeResult:
        with Horizontal(classes="with-border"):  # (1)!