Skip to content

Progress

The Progress component creates visual progress bars to show completion status. Perfect for uploads, downloads, multi-step forms, and any process with measurable progress.

Goal

Master creating progress bars, understand Bootstrap progress classes, and build engaging progress indicators that keep users informed.


Quick Start

Live Preview
75%
from faststrap import Progress

Progress(75, label="75%")

Visual Examples & Use Cases

1. Color Variants - Indicate Status

Use colors to show different states or priorities.

Live Preview
25%
50%
75%
Complete!
Progress(25, variant="success", label="25%")
Progress(50, variant="info", label="50%")
Progress(75, variant="warning", label="75%")
Progress(100, variant="danger", label="Complete!")

2. Striped & Animated - Add Visual Interest

Make progress bars more engaging with stripes and animation.

Live Preview
# Striped progress bar
Progress(60, variant="primary", striped=True)

# Animated striped progress bar
Progress(75, variant="success", striped=True, animated=True)

When to use animation: - ✅ Active uploads/downloads - ✅ Processing operations - ✅ Real-time updates - ❌ Static completion percentages - ❌ Historical data


3. Custom Heights - Match Your Design

Adjust bar height for different contexts.

Live Preview
50%
# Thin - subtle indicators
Progress(50, variant="primary", height="5px")

# Default - standard use
Progress(50, variant="info")

# Thick - prominent displays, with labels
Progress(50, variant="success", height="30px", label="50%")

4. Stacked Progress - Multiple Segments

Show multiple progress segments in one bar.

Live Preview
30%
20%
15%
from faststrap import ProgressBar
from fasthtml.common import Div

# Stacked progress bars
Div(
    ProgressBar(30, variant="success", label="30%"),
    ProgressBar(20, variant="warning", label="20%"),
    ProgressBar(15, variant="danger", label="15%"),
    cls="progress"
)

Perfect for: - Storage usage (used/available/reserved) - Task completion by category - Resource allocation - Survey responses by option


Practical Functionality

File Upload Progress with HTMX

Show real-time upload progress.

from faststrap import Progress, Card, Button
from fasthtml.common import Div, Form, Input

@app.get("/")
def home():
    return Card(
        Form(
            Input("file", input_type="file", cls="form-control mb-3"),
            Button("Upload", type="submit", variant="primary"),
            hx_post="/upload",
            hx_target="#upload-progress",
            hx_encoding="multipart/form-data"
        ),
        Div(id="upload-progress"),
        header="File Upload"
    )

@app.post("/upload")
async def upload(file: UploadFile):
    # Simulate chunked upload with progress updates
    total_size = file.size
    uploaded = 0

    # Return progress updates via SSE or polling
    return Progress(
        uploaded,
        max_value=total_size,
        variant="success",
        striped=True,
        animated=True,
        label=f"{(uploaded/total_size)*100:.0f}%"
    )

Multi-Step Form Progress

Show users where they are in a multi-step process.

from faststrap import Progress, Card, Button, Fx

def StepIndicator(current_step: int, total_steps: int):
    progress_pct = (current_step / total_steps) * 100

    return Card(
        Div(
            Div(
                f"Step {current_step} of {total_steps}",
                cls="text-muted small mb-2"
            ),
            Progress(
                progress_pct,
                variant="primary",
                height="8px",
                cls=Fx.fade_in
            ),
            cls="mb-4"
        ),
        # Form content here
        Div(
            Button("Previous", variant="outline-secondary", 
                   hx_get=f"/step/{current_step-1}" if current_step > 1 else None,
                   disabled=current_step == 1),
            Button("Next", variant="primary",
                   hx_get=f"/step/{current_step+1}" if current_step < total_steps else None),
            cls="d-flex justify-content-between"
        )
    )

@app.get("/step/{step}")
def show_step(step: int):
    return StepIndicator(step, total_steps=5)

Dynamic Progress Updates

Update progress in real-time with HTMX polling.

@app.get("/")
def home():
    return Div(
        Button(
            "Start Task",
            hx_post="/start-task",
            hx_target="#progress-container"
        ),
        Div(id="progress-container")
    )

@app.post("/start-task")
def start_task():
    # Start background task
    task_id = start_background_task()

    # Return progress bar that polls for updates
    return Div(
        Progress(0, variant="primary", striped=True, animated=True, label="0%"),
        id="task-progress",
        hx_get=f"/task-status/{task_id}",
        hx_trigger="every 1s",  # Poll every second
        hx_swap="outerHTML"
    )

@app.get("/task-status/{task_id}")
def task_status(task_id: str):
    progress = get_task_progress(task_id)  # 0-100

    if progress >= 100:
        return Div("Task complete!", cls="alert alert-success")

    return Div(
        Progress(progress, variant="primary", striped=True, animated=True, 
                label=f"{progress}%"),
        id="task-progress",
        hx_get=f"/task-status/{task_id}",
        hx_trigger="every 1s",
        hx_swap="outerHTML"
    )

Bootstrap CSS Classes Explained

Core Progress Classes

Class Purpose Effect
.progress Container - Wraps progress bar(s) Gray background, rounded corners
.progress-bar Bar - The colored progress indicator Fills container based on width
.bg-{variant} Color - Applies variant color primary, success, danger, etc.
.progress-bar-striped Stripes - Diagonal stripe pattern Visual interest
.progress-bar-animated Animation - Animates stripes Moving stripes effect

Sizing & Layout

Class Purpose Use Case
style="height: Xpx" Custom height - On .progress container Thin/thick bars
style="width: X%" Progress value - On .progress-bar Actual progress
.mb-3 Margin bottom - Spacing between bars Multiple progress bars

Accessibility Classes

Attribute Purpose Value
role="progressbar" ARIA role - Identifies as progress Always present
aria-valuenow Current value - For screen readers 0-100
aria-valuemin Minimum value - Usually 0 0
aria-valuemax Maximum value - Usually 100 100

Responsive Progress Patterns

Mobile-Friendly Progress

from faststrap import Progress, Container, Row, Col

Container(
    Row(
        Col(
            Progress(
                75,
                variant="primary",
                height="20px",  # Larger for touch visibility
                label="75%",
                cls="mb-3"
            ),
            cols=12  # Full width on mobile
        )
    )
)

Conditional Styling Based on Progress

def SmartProgress(value: int):
    # Change color based on progress
    if value < 30:
        variant = "danger"
    elif value < 70:
        variant = "warning"
    else:
        variant="success"

    return Progress(
        value,
        variant=variant,
        striped=True,
        animated=value < 100,  # Stop animation when complete
        label=f"{value}%"
    )

Core Faststrap Features

Global Defaults with set_component_defaults

from faststrap import set_component_defaults, Progress

# All progress bars use success variant and stripes
set_component_defaults("Progress", variant="success", striped=True)

# Now all progress bars inherit these defaults
Progress(50)  # ← Automatically success + striped

# Override when needed
Progress(25, variant="danger", striped=False)

Common Default Patterns:

# Upload-heavy apps - animated progress
set_component_defaults("Progress", striped=True, animated=True)

# Thin progress indicators
set_component_defaults("Progress", height="5px")

# Success-themed apps
set_component_defaults("Progress", variant="success")

Common Recipes

The "Storage Usage" Pattern

Show storage capacity with stacked bars.

def StorageIndicator(used_gb: float, total_gb: float):
    used_pct = (used_gb / total_gb) * 100
    free_pct = 100 - used_pct

    return Div(
        Div(
            f"{used_gb:.1f} GB of {total_gb} GB used",
            cls="text-muted small mb-2"
        ),
        Div(
            ProgressBar(used_pct, variant="primary"),
            ProgressBar(free_pct, variant="light"),
            cls="progress"
        )
    )

The "Skill Level" Pattern

Show proficiency levels.

def SkillBar(skill: str, level: int):
    return Div(
        Div(
            skill,
            Badge(f"{level}%", variant="secondary", cls="float-end"),
            cls="mb-1"
        ),
        Progress(level, variant="info", height="8px"),
        cls="mb-3"
    )

# Usage
SkillBar("Python", 90)
SkillBar("JavaScript", 75)
SkillBar("SQL", 85)

Parameter Reference

Parameter Type Default Description
value int Required Current progress value
max_value int 100 Maximum value
variant VariantType \| None "primary" Color variant
striped bool \| None False Use striped style
animated bool \| None False Animate stripes (requires striped=True)
label str \| None None Label text to display
height str \| None None Custom height (e.g., "20px")
**kwargs Any - Additional HTML attributes (cls, id, style)

faststrap.components.feedback.progress.Progress(value, max_value=100, variant=None, striped=None, animated=None, label=None, height=None, **kwargs)

Bootstrap Progress component for progress bars.

Parameters:

Name Type Description Default
value int

Current progress value

required
max_value int

Maximum value (default 100)

100
variant VariantType | None

Bootstrap color variant

None
striped bool | None

Use striped style

None
animated bool | None

Animate stripes (requires striped=True)

None
label str | None

Label text to display

None
height str | None

Custom height (e.g., "20px")

None
**kwargs Any

Additional HTML attributes

{}
Source code in src/faststrap/components/feedback/progress.py
@register(category="feedback")
def Progress(
    value: int,
    max_value: int = 100,
    variant: VariantType | None = None,
    striped: bool | None = None,
    animated: bool | None = None,
    label: str | None = None,
    height: str | None = None,
    **kwargs: Any,
) -> Div:
    """Bootstrap Progress component for progress bars.

    Args:
        value: Current progress value
        max_value: Maximum value (default 100)
        variant: Bootstrap color variant
        striped: Use striped style
        animated: Animate stripes (requires striped=True)
        label: Label text to display
        height: Custom height (e.g., "20px")
        **kwargs: Additional HTML attributes
    """
    # Resolve API defaults
    cfg = resolve_defaults("Progress", variant=variant, striped=striped, animated=animated)

    c_variant = cfg.get("variant", "primary")
    c_striped = cfg.get("striped", False)
    c_animated = cfg.get("animated", False)

    # Calculate percentage (guard against zero/negative max)
    if max_value <= 0:
        pct: float = 0.0
    else:
        pct = min(100.0, max(0.0, (value / max_value) * 100))

    # Build bar classes
    bar_classes = ["progress-bar"]
    if c_variant:
        bar_classes.append(f"bg-{c_variant}")
    if c_striped:
        bar_classes.append("progress-bar-striped")
    if c_animated:
        bar_classes.append("progress-bar-animated")

    # Create progress bar
    bar = Div(
        label or "",
        cls=" ".join(bar_classes),
        role="progressbar",
        aria_valuenow=value,
        aria_valuemin=0,
        aria_valuemax=max_value,
        style=f"width: {pct}%",
    )

    # Build wrapper
    user_cls = kwargs.pop("cls", "")
    wrapper_cls = merge_classes("progress", user_cls)

    wrapper_attrs: dict[str, Any] = {"cls": wrapper_cls}
    if height:
        wrapper_attrs["style"] = f"height: {height}"

    # Convert remaining kwargs
    wrapper_attrs.update(convert_attrs(kwargs))

    return Div(bar, **wrapper_attrs)

faststrap.components.feedback.progress.ProgressBar(value, max_value=100, variant=None, striped=None, animated=None, label=None, **kwargs)

Individual progress bar for stacked progress bars.

Source code in src/faststrap/components/feedback/progress.py
@register(category="feedback")
def ProgressBar(
    value: int,
    max_value: int = 100,
    variant: VariantType | None = None,
    striped: bool | None = None,
    animated: bool | None = None,
    label: str | None = None,
    **kwargs: Any,
) -> Div:
    """Individual progress bar for stacked progress bars."""
    # Resolve API defaults
    cfg = resolve_defaults(
        "Progress", variant=variant, striped=striped, animated=animated  # Reusing Progress defaults
    )

    c_variant = cfg.get("variant", "primary")
    c_striped = cfg.get("striped", False)
    c_animated = cfg.get("animated", False)

    # Calculate percentage (guard against zero/negative max)
    if max_value <= 0:
        pct: float = 0.0
    else:
        pct = min(100.0, max(0.0, (value / max_value) * 100))

    # Build classes
    classes = ["progress-bar"]
    if c_variant:
        classes.append(f"bg-{c_variant}")
    if c_striped:
        classes.append("progress-bar-striped")
    if c_animated:
        classes.append("progress-bar-animated")

    # Merge with user classes
    user_cls = kwargs.pop("cls", "")
    cls = merge_classes(" ".join(classes), user_cls)

    # Build attributes
    attrs: dict[str, Any] = {
        "cls": cls,
        "role": "progressbar",
        "aria_valuenow": value,
        "aria_valuemin": 0,
        "aria_valuemax": max_value,
        "style": f"width: {pct}%",
    }

    # Convert remaining kwargs
    attrs.update(convert_attrs(kwargs))

    return Div(label or "", **attrs)