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;
}

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.


textual.widgets.ProgressBar class

def __init__(
    self,
    total=None,
    *,
    show_bar=True,
    show_percentage=True,
    show_eta=True,
    name=None,
    id=None,
    classes=None,
    disabled=False,
    clock=None
):

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
Parameter Default Description
total
float | None
None

The total number of steps in the progress if known.

show_bar
bool
True

Whether to show the bar portion of the progress bar.

show_percentage
bool
True

Whether to show the percentage status of the bar.

show_eta
bool
True

Whether to show the ETA countdown of the progress bar.

name
str | None
None

The name of the widget.

id
str | None
None

The ID of the widget in the DOM.

classes
str | None
None

The CSS classes for the widget.

disabled
bool
False

Whether the widget is disabled or not.

clock
Clock | None
None

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

percentage class-attribute instance-attribute

percentage: reactive[float | None] = 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[float] = reactive(0.0)

The progress so far, in number of steps.

total class-attribute instance-attribute

total: reactive[float | None] = total

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

The value None will render an indeterminate progress bar.

advance method

def advance(self, advance=1):

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

Example
progress_bar.advance(10)  # Advance 10 steps.
Parameters
Parameter Default Description
advance
float
1

Number of steps to advance progress by.

update method

def update(
    self, *, 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
Parameter Default Description
total
None | float | UnusedParameter
UNUSED

New total number of steps.

progress
float | UnusedParameter
UNUSED

Set the progress to the given number of steps.

advance
float | UnusedParameter
UNUSED

Advance the progress by this number of steps.