Skip to content

ProgressBar

A widget that displays progress on a time-consuming task.

  • Focusable
  • Container

Examples

Progress Bar in Isolation

The example below shows a progress bar in isolation. It shows the progress bar in:

  • its indeterminate state, when the total progress hasn't been set yet;
  • the middle of the progress; and
  • the completed state.

IndeterminateProgressBar ━╸━━━━━━━━╺━━━━━━━━━━━━━━━━━━━━━--%--:--:--                   s Start 

IndeterminateProgressBar ━━━━━━━━━━━━╺━━━━━━━━━━━━━━━━━━━39%00:00:07                   s Start 

IndeterminateProgressBar ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━100%--:--:--                   s Start 

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


class IndeterminateProgressBar(App[None]):
    BINDINGS = [("s", "start", "Start")]

    progress_timer: Timer
    """Timer to simulate progress happening."""

    def compose(self) -> ComposeResult:
        with Center():
            with Middle():
                yield ProgressBar()
        yield Footer()

    def on_mount(self) -> None:
        """Set up a timer to simulate progess happening."""
        self.progress_timer = self.set_interval(1 / 10, self.make_progress, pause=True)

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

    def action_start(self) -> None:
        """Start the progress tracking."""
        self.query_one(ProgressBar).update(total=100)
        self.progress_timer.resume()


if __name__ == "__main__":
    IndeterminateProgressBar().run()

Complete App Example

The example below shows a simple app with a progress bar that is keeping track of a fictitious funding level for an organisation.

Funding tracking Funding tracking                Funding: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━0%                 ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ $$$ Donate  ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

Funding tracking Funding tracking                Funding: ━━━━━━━━━━━╺━━━━━━━━━━━━━━━━━━━━35%                 ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ $$$ Donate  ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ Donation for $15 received!                                                       Donation for $20 received!                                                      

Funding tracking Funding tracking                Funding: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━100%                 ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ $$$ Donate  ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ Donation for $15 received!                                                       Donation for $20 received!                                                       Donation for $65 received!                                                      

from textual.app import App, ComposeResult
from textual.containers import Center, VerticalScroll
from textual.widgets import Button, Header, Input, Label, ProgressBar


class FundingProgressApp(App[None]):
    CSS_PATH = "progress_bar.tcss"

    TITLE = "Funding tracking"

    def compose(self) -> ComposeResult:
        yield Header()
        with Center():
            yield Label("Funding: ")
            yield ProgressBar(total=100, show_eta=False)  # (1)!
        with Center():
            yield Input(placeholder="$$$")
            yield Button("Donate")

        yield VerticalScroll(id="history")

    def on_button_pressed(self) -> None:
        self.add_donation()

    def on_input_submitted(self) -> None:
        self.add_donation()

    def add_donation(self) -> None:
        text_value = self.query_one(Input).value
        try:
            value = int(text_value)
        except ValueError:
            return
        self.query_one(ProgressBar).advance(value)
        self.query_one(VerticalScroll).mount(Label(f"Donation for ${value} received!"))
        self.query_one(Input).value = ""


if __name__ == "__main__":
    FundingProgressApp().run()
  1. We create a progress bar with a total of 100 steps and we hide the ETA countdown because we are not keeping track of a continuous, uninterrupted task.
Container {
    overflow: hidden hidden;
    height: auto;
}

Center {
    margin-top: 1;
    margin-bottom: 1;
    layout: horizontal;
}

ProgressBar {
    padding-left: 3;
}

Input {
    width: 16;
}

VerticalScroll {
    height: auto;
}

Gradient Bars

Progress bars support an optional gradient parameter, which renders a smooth gradient rather than a solid bar. To use a gradient, create and set a Gradient object on the ProgressBar widget.

Note

Setting a gradient will override styles set in CSS.

Here's an example:

ProgressApp ━━━━━━━━━70%--:--:--                 

from textual.app import App, ComposeResult
from textual.color import Gradient
from textual.containers import Center, Middle
from textual.widgets import ProgressBar


class ProgressApp(App[None]):
    """Progress bar with a rainbow gradient."""

    def compose(self) -> ComposeResult:
        gradient = Gradient.from_colors(
            "#881177",
            "#aa3355",
            "#cc6666",
            "#ee9944",
            "#eedd00",
            "#99dd55",
            "#44dd88",
            "#22ccbb",
            "#00bbcc",
            "#0099cc",
            "#3366bb",
            "#663399",
        )
        with Center():
            with Middle():
                yield ProgressBar(total=100, gradient=gradient)

    def on_mount(self) -> None:
        self.query_one(ProgressBar).update(progress=70)


if __name__ == "__main__":
    ProgressApp().run()

Custom Styling

This shows a progress bar with custom styling. Refer to the section below for more information.

StyledProgressBar ━╸━━━━━━━━╺━━━━━━━━━━━━━━━━━━━━━--%--:--:--  s Start 

StyledProgressBar ━━━━━━━━━━━━╺━━━━━━━━━━━━━━━━━━━39%00:00:07  s Start 

StyledProgressBar ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━100%--:--:--  s Start 

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


class StyledProgressBar(App[None]):
    BINDINGS = [("s", "start", "Start")]
    CSS_PATH = "progress_bar_styled.tcss"

    progress_timer: Timer
    """Timer to simulate progress happening."""

    def compose(self) -> ComposeResult:
        with Center():
            with Middle():
                yield ProgressBar()
        yield Footer()

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

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

    def action_start(self) -> None:
        """Start the progress tracking."""
        self.query_one(ProgressBar).update(total=100)
        self.progress_timer.resume()


if __name__ == "__main__":
    StyledProgressBar().run()
Bar > .bar--indeterminate {
    color: $primary;
    background: $secondary;
}

Bar > .bar--bar {
    color: $primary;
    background: $primary 30%;
}

Bar > .bar--complete {
    color: $error;
}

PercentageStatus {
    text-style: reverse;
    color: $secondary;
}

ETAStatus {
    text-style: underline;
}

Styling the Progress Bar

The progress bar is composed of three sub-widgets that can be styled independently:

Widget name ID Description
Bar #bar The bar that visually represents the progress made.
PercentageStatus #percentage Label that shows the percentage of completion.
ETAStatus #eta Label that shows the estimated time to completion.

Bar Component Classes

The bar sub-widget provides the component classes that follow.

These component classes let you modify the foreground and background color of the bar in its different states.

Class Description
bar--bar Style of the bar (may be used to change the color).
bar--complete Style of the bar when it's complete.
bar--indeterminate Style of the bar when it's in an indeterminate state.

Reactive Attributes

Name Type Default Description
percentage float | None The read-only percentage of progress that has been made. This is None if the total hasn't been set.
progress float 0 The number of steps of progress already made.
total float | None The total number of steps that we are keeping track of.

Messages

This widget posts no messages.

Bindings

This widget has no bindings.

Component Classes

This widget has no component classes.


Bases: Widget

A progress bar widget.

The progress bar uses "steps" as the measurement unit.

Example
class MyApp(App):
    def compose(self):
        yield ProgressBar(total=100)

    def key_space(self):
        self.query_one(ProgressBar).advance(5)

Parameters:

Name Type Description Default

total

float | None

The total number of steps in the progress if known.

None

show_bar

bool

Whether to show the bar portion of the progress bar.

True

show_percentage

bool

Whether to show the percentage status of the bar.

True

show_eta

bool

Whether to show the ETA countdown of the progress bar.

True

name

str | None

The name of the widget.

None

id

str | None

The ID of the widget in the DOM.

None

classes

str | None

The CSS classes for the widget.

None

disabled

bool

Whether the widget is disabled or not.

False

clock

Clock | None

An optional clock object (leave as default unless testing).

None

gradient

Gradient | None

An optional Gradient object (will replace CSS styles in the bar).

None

gradient class-attribute instance-attribute

gradient = reactive(None)

Optional gradient object (will replace CSS styling in bar).

percentage class-attribute instance-attribute

percentage = reactive[Optional[float]](None)

The percentage of progress that has been completed.

The percentage is a value between 0 and 1 and the returned value is only None if the total progress of the bar hasn't been set yet.

Example
progress_bar = ProgressBar()
print(progress_bar.percentage)  # None
progress_bar.update(total=100)
progress_bar.advance(50)
print(progress_bar.percentage)  # 0.5

progress class-attribute instance-attribute

progress = reactive(0.0)

The progress so far, in number of steps.

total class-attribute instance-attribute

total = total

The total number of steps associated with this progress bar, when known.

The value None will render an indeterminate progress bar.

advance

advance(advance=1)

Advance the progress of the progress bar by the given amount.

Example
progress_bar.advance(10)  # Advance 10 steps.

Parameters:

Name Type Description Default

advance

float

Number of steps to advance progress by.

1

update

update(*, total=UNUSED, progress=UNUSED, advance=UNUSED)

Update the progress bar with the given options.

Example
progress_bar.update(
    total=200,  # Set new total to 200 steps.
    progress=50,  # Set the progress to 50 (out of 200).
)

Parameters:

Name Type Description Default

total

None | float | UnusedParameter

New total number of steps.

UNUSED

progress

float | UnusedParameter

Set the progress to the given number of steps.

UNUSED

advance

float | UnusedParameter

Advance the progress by this number of steps.

UNUSED