Creating Progress Bars in Your Terminal with Python and Textual

The Textual package is a great way to create GUI-like applications with Python in your terminal. These are known as text-based user interfaces or TUIs. Textual has many different widgets built-in to the framework.

One of those widgets is the ProgressBar. If you need to show the progress of a download or long-running process, then you will probably want to use a progress bar or some kind of spinner widget to show the user that your application is working.

This tutorial will show you how to create a simple progress bar with Textual!

Installation

If you do not have Textual installed yet, you can get it by using Python’s handy pip tool. Open up your terminal and run the following command there:

python -m pip install textual

Textual will get installed, along with all its dependencies. You may want to create a virtual environment first and install Textual there.

Creating a ProgressBar in Textual

Creating a progress bar with Textual is pretty straightforward. The following code is based on an example from the Textual documentation. The main difference is that this version uses a button to start the timer, simulating a download or some other long-running process rather than catching a key event.

You can copy this code into your favorite Python editor and give it a quick study:

# progressbar_demo.py

from textual.app import App, ComposeResult
from textual.containers import Center, Middle
from textual.timer import Timer
from textual.widgets import Button, ProgressBar

class Progress(App[None]):
    timer = Timer

    def compose(self) -> ComposeResult:
        with Center():
            with Middle():
                yield ProgressBar()
                yield Button("Start")

    def on_mount(self) -> None:
        """
        Set up the timer to simulate progress
        """
        self.timer = self.set_interval(1 / 10, self.advance_progressbar, pause=True)

    def advance_progressbar(self) -> None:
        """
        Called to advance the progress bar
        """
        self.query_one(ProgressBar).advance(1)

    def on_button_pressed(self) -> None:
        """
        Event handler that is called when button is pressed
        """
        self.query_one(ProgressBar).update(total=100)
        self.timer.resume()

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

The compose() method is kind of fun as it uses both the Center() and the Middle() containers to position the widgets in the middle of the terminal. You then set up the timer object in on_mount() and you start the timer in on_button_pressed()which is the Button widget’s event handler.

When you run this code, you will initially see what’s known as an indeterminate progress bar. What that means is that Textual doesn’t have any information about how long the progress is, so the progress bar just shows a kind of “bouncing” or “cycling” progress:

When you press the “Start” button, you will see the progress bar go from 0-100%, and the text fields to the right will update as well:

If you want to make your progress bar stand out, add a gradient. A gradient will make the colors of the progress bar change over time and make the widget look neat!

Wrapping Up

Adding a progress bar or simply showing some kind of informational widget that lets the user know your application is working is always a good idea. You don’t want the user to think the application has crashed or frozen. The user might be forced to close the application and lose their information after all!

Fortunately, Textual makes adding a progress bar easy. Not only is it easy, but the progress bar is easy to update so you can accurately give the user a sense of when the work will be done. Give it a try and see what you think!

Related Reading

Want to learn more about Textual? Check out the following articles: