Progress indicators
Visual feedback that an operation is in progress — linear (bar) or circular (spinner), determinate (known %) or indeterminate (unknown duration). Material parity (`/components/progress-indicators`).
Spec — Progress indicators
Facet *isual*of Koder Design. Material parity: https://m3.material.io/components/progress-indicators.
2 shapes × 2 modes
| Shape Mode | Determinate | Indeterminate |
|---|---|---|
| *inear* | Bar fills 0 → 100% | Sliding band animates left → right (no fill) |
| *ircular* | Arc sweeps 0 → 360° | Arc rotates continuously |
Pick:
- *hape* linear when there's horizontal real estate (above content,
banner-like); circular when space is constrained (button, dialog, icon-sized).
- *ode* determinate when % is known (file upload); indeterminate
when not (network handshake, ML inference).
Anatomy (linear, determinate)
45% complete
████████░░░░░░░░░░░░░░░░░░░░░
←──────── 100% ────────────→- *rack height* 4 px (default) / 8 px (large)
- *rack bg*
surface-container-highest(orprimary12%) - *ctive fill*
primarycolor - *orner radius* track height / 2 (pill shape)
- *top point*(Material 3): 4 px circle of
primarycolor at theend of the track to indicate "track end"
Anatomy (circular):
╭ ━━━╮
┃ ╲
┃ ● ← stroke head
╲ ╱
╲ ━╱- *iameter* 24 px (small) / 32 px (default) / 48 px (large)
- *troke width* 4 px (default), scales with size
- *rack*
surface-container-highestring - *ctive arc*
primarycolor
R1 — Linear determinate
- Fill animates from current % to new % (motion-fast, ~150 ms)
- Don't reset to 0 between updates — smooth interpolation
- At 100%: brief settle (stop-point disappears as track ends)
- Backwards progress (resume after pause) allowed — animate back
R2 — Linear indeterminate
- Sliding band: 30-50% of track width, slides left → right
continuously
- Cycle duration: 2 s
- Easing: cubic ease
inout - No stop point in indeterminate mode
R3 — Circular determinate
- Arc length proportional to %
- Starts at 12 o'clock; sweeps clockwise
- Stroke ends with rounded cap
R4 — Circular indeterminate
- Full circle rotates clockwise: 1.5 s per rotation
- Arc length pulses: 25% → 75% → 25% over 1 cycle
- Two pulses per rotation (Material 3 default; document if changed)
R5 — Buffer indicator (linear only)
For media / download with "buffered ahead of playhead":
████████████░░░░░░░░░░░░░░░░░░░
━━━━━━━━━━━━━━━━━░░░░░░░░░░░░░░
↑ ↑
playhead (filled)
buffered (lighter)- Buffer color:
primary40% (lighter than active) - Buffer always ≥ playhead position
R6 — Indicator placement
| Placement | Use |
|---|---|
| *bove content*(full-width linear) | Page-level loading |
| *n-button*(small circular replaces text) | Submitting a form |
| *nline next to label*(24 px circular) | Item-level operation (e.g., row syncing) |
| *entered in container*(large circular) | Empty card / pane loading |
| *n app bar bottom edge*(linear) | Long page operations (refresh) |
R7 — Combine with action button
When a button triggers a long operation, replace its content with a small circular indicator (24 px):
Before: [ Submit ]
During: [ ◐ ] ← circular indeterminate, 24 px
After: [✓ Saved ]- Button width stays the same (avoid layout shift)
- Disable the button during operation
- On success: brief check icon (1 s) before reverting to idle / new
label
R8 — Multi-step / segmented (linear)
Linear can show discrete steps:
███ ███ ███ ░░░ ░░░ Step 3 of 5
step1 step2 step3 step4 step5- 2 px gap between segments
- Each segment radius = track height / 2
- Filled segments use active color; unfilled use track color
Used for: onboarding stepper, multi-page form indicator.
R9 — Empty / not-started state
Don't show the indicator at 0% — looks broken. Either:
- Show indicator only after operation starts (delay 200-400 ms)
- Begin animation as soon as visible (indeterminate)
For determinate, if value is genuinely 0, render the track only (no fill) and show "0%" or "Not started" label nearby.
R10 — Completion state
Determinate at 100%:
- Brief settle (200 ms)
- Fade out indicator OR replace with success icon (✓)
- For background operations, fade out after 1-2 s
R11 — Accessibility
- Determinate linear / circular:
role="progressbar"+aria-valuenow/aria-valuemin/aria-valuemax+aria-labeldescribing what's loading - Indeterminate:
role="progressbar"+aria-busy="true"+ noaria-valuenow(omitted means indeterminate per ARIA) - For long operations, periodically announce progress to screen reader
(e.g., every 25%):
aria-live="polite"on container - Don't rely on color alone — pair with text label ("45% complete")
for determinate
R12 — Animation
- Determinate update: motion-fast (~150 ms) interpolation
- Indeterminate linear cycle: 2 s
- Indeterminate circular cycle: 1.5 s
- All animations respect
prefers-reduced-motion:- Linear indeterminate: pulsing opacity 50% → 100% instead of sliding
- Circular indeterminate: no rotation; opacity pulse instead
R13 — States
Progress indicators don't have hover / pressed / focused states (they're decorative output, not interactive).
Disabled state IS valid — operation cancelled:
- Indicator fades to 38% opacity
- Active animation stops
- Track stays visible briefly before fading entirely
R14 — Density
| Density | Linear height | Circular default |
|---|---|---|
| Compact | 2 px | 20 px |
| Default | 4 px | 32 px |
| Comfortable | 8 px | 48 px |
R15 — Per-preset variation
| Preset | Linear | Circular | |
|---|---|---|---|
material3 |
Track + stop point at end | Stroke with rounded cap, 2 pulses/rotation | |
material2 |
No stop point; flat ends | Full rotation, 1 pulse | |
ios_cupertino |
Solid bar without stop | UIActivityIndicator gear-style | |
gnome |
GtkProgressBar style | Adwaita spinner (3 dots) | |
windows_11 |
Accent bar with subtle glow | Fluent spinner (dots circling) | |
brutalist |
Solid block, no easing (step changes only) | Square instead of circle, hard rotation | |
terminal_classic |
[██████░░░░] 60% ASCII |
Spinner cycle ` | /-` |
R16 — Forbidden patterns
- ❌ Indeterminate when % is actually known (defeats user trust)
- ❌ Multiple progress indicators stacked on the same operation
- ❌ Indicator < 24 px for circular (unreadable)
- ❌ Animation that resets to 0 between % updates
- ❌ Showing indicator for < 200 ms (annoying flash; either delay it
or do without)
- ❌ Operation that runs > 30 s with no progress feedback (always
show indicator)
- ❌ Indicator without label / context (user can't tell WHAT is
loading)
- ❌ Hiding the button label entirely on submit without a
replacement (no feedback that work started)
Cross-link
themes/motion.kmd— easing for indeterminate cyclethemes/color-roles.kmd—primaryfor active,surface-container-highestfor trackinteraction/states.kmd— disabled overlaycomponents/buttons.kmd— in-button progress patternfoundations/elements.kmd— Output family