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:
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:
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:
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:
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:
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 Vertical
, Horizontal
, 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 Center
, Middle
, Horizontal
, 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:
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:
The real benefit using containers comes when you nest them. You’ll find out about that concept next!
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:
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:
Nice! You should spend some time adjusting the style rules and seeing how to change these layouts.
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.
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!