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: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━0% ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ $$$Donate ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

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

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.css"

    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.css"

    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__":
    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;
}

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.

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.

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
):

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

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. Once total is set to a numerical value, it cannot be set back to None.

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
Name Type Description Default
advance float

Number of steps to advance progress by.

1

compute_percentage method

def compute_percentage(self):

Keep the percentage of progress updated automatically.

This will report a percentage of 1 if the total is zero.

update method

def update(self, *, total=None, progress=None, advance=None):

Update the progress bar with the given options.

Options only affect the progress bar if they are not None.

Example
progress_bar.update(
    total=200,  # Set new total to 200 steps.
    progress=None,  # This has no effect.
)
Parameters
Name Type Description Default
total float | None

New total number of steps (if not None).

None
progress float | None

Set the progress to the given number of steps (if not None).

None
advance float | None

Advance the progress by this number of steps (if not None).

None

validate_progress method

def validate_progress(self, progress):

Clamp the progress between 0 and the maximum total.

validate_total method

def validate_total(self, total):

Ensure the total is not negative.

watch_total method

def watch_total(self, total):

Re-validate progress.