Designer view

Progress

A non-interactive indicator showing the completion status of an ongoing task — uploads, downloads, multi-step server operations, long form submissions. Carries `role="progressbar"` plus an accessible name; determinate progress reports `aria-valuemin` / `aria-valuemax` / `aria-valuenow` so AT users hear the percentage; indeterminate progress omits `aria-valuenow` per APG and announces a busy state. Two structural variants (linear bar, circular ring) cover the canonical visual shapes; native HTML `<progress>` is an acceptable alternative implementation when no styling control is needed.

Also called Progress bar Progress indicator Loader

When to use

Use

For tasks with a measurable completion fraction (file upload showing 0-100%, multi-step server-side operation reporting step N of M) — render the determinate variant with `aria-valuenow` reporting the percentage. For tasks with no measurable fraction but where the user must know the system is working (server fetch with unknown latency, background sync) — render the indeterminate variant with a reduced-motion fallback. Linear variant for in-page progress that has horizontal space; circular variant for button-adjacent or compact contexts.

Avoid

For loading states where the eventual layout is known — that is `Skeleton` (preserves layout, no progress meaning). For transient post-completion notifications — that is `Toast` (system-pushed signal). For static completion messages once a task is done — that is `role="status"` or `<output>`, not `progressbar`. For multi-step user-driven workflows with state carried between steps — that is `Stepper`. For purely decorative spinning visuals not tied to a real task — render no progress affordance; loaders without backing operations confuse AT users about whether the system is working.

Versus related

  • skeleton

    `Skeleton` reserves layout while the eventual content is fetched — placeholder shapes mirror the final layout, no percentage. `Progress` reports the completion fraction (or busy state) of an ongoing task — no layout preservation, just status. Choose Skeleton when the layout is the message ("the page will look like this"); choose Progress when the completion is the message ("we are 60% through"). A single load may use both — Skeleton in the data region, Progress in a header showing overall completion across multiple loads.

  • toast

    `Toast` is a transient post-completion notification ("Upload complete"); `Progress` is the during-task indicator that the toast typically follows. Skeleton-during-load → Progress-while-working → Toast- on-complete is a canonical pairing across a long- running operation. Toast carries notification semantics (`role="status"` or `aria-live="polite"`); Progress carries progress semantics (`role="progressbar"` plus `aria-valuenow`).

Progress communicates "this is working" plus, when known, "this is how far along". The reference documents the `role="progressbar"` contract, the accessible-name requirement (`aria-label` or `aria-labelledby` — never unlabeled), the determinate-vs-indeterminate split (omit `aria-valuenow` for indeterminate per APG), the `prefers-reduced-motion` fallback for indeterminate animation (WCAG 2.3.3), and the boundary with `Skeleton` (use Skeleton when the eventual layout is preserved and progress is unmeasurable; use Progress when there is a measurable percentage or a busy state to surface).

Highlight
Fig 1.1 · Progress · Designer view
Designer

Figma anatomy

Slot Figma type Hint
root frame Progressbar frame; variant property switches between linear and circular shapes
track instance Track shape — bar (linear) or ring (circular); muted color
fill instance Fill shape — bar (linear) or arc (circular); accent color; animation per variant
label text Label text style; positioned above or beside the progress shape
value-display text Numeric value text; aligned trailing (linear) or centered (circular)
status-icon instance Icon instance — checkmark (complete) or x (error); aria-hidden
helper-text text Helper text style; muted color; positioned below progress shape
announcement text Visually-hidden text element for milestone announcements
Designer

Token usage per slot

track
color
  • backgroundcolor.surface.sunken
fill
color
  • backgroundcolor.accent.bg
label
typography
  • sizetext.sm
value-display
typography
  • sizetext.sm
  • weightweight.semibold
helper-text
color
  • foregroundcolor.text.muted
typography
  • sizetext.sm
Both

Figma ↔ Code property map

FigmaKindCodeNotes
VariantEnumvariantMaps linear / circular structural shapes.
SizeEnumsizesm / md / lg.
DeterminateBooleandeterminatePositive-default boolean; true reports `aria-valuenow`, false omits it (indeterminate mode).
Show ValueBooleanshowValueControls visibility of the value-display slot. AT announcement uses aria-valuenow regardless.
ValueNumbervalue0..max; reflects to `aria-valuenow`. Required when determinate; ignored when indeterminate.
MaxNumbermaxDefaults to 100. Reflects to `aria-valuemax`. Custom max with `aria-valuetext` for non-percent contexts (steps, bytes).
Aria LabelTextariaLabelAccessible name. One of ariaLabel / ariaLabelledby is required.
Aria ValuetextTextariaValuetextOptional human-readable value override for non-percent contexts ("Step 3 of 7", "2 MB of 5 MB").
StatusEnumstatuspending / inProgress / complete / error / indeterminate. Drives status-icon visibility and aria-valuetext.
LabelSlotlabelVisible label slot; alternative to ariaLabel attribute.
Helper TextSlothelperTextOptional secondary descriptive text below the progress shape.
Designer

Motion

TransitionDuration token
valueTransitionmotion.duration.fast
indeterminateLoopmotion.duration.slower
Easing
motion.easing.standard
Reduced motion
Reduced (shortened)
Designer

Responsive behaviour

BreakpointChange
breakpoint.smBelow this width, the linear variant collapses to full-inline-width (loses any authored max- width); the circular variant scales to the container's `block-size`. Helper-text and value-display may stack below the progress shape rather than sitting beside it.
breakpoint.mdAt and above this width, both variants render at the authored size with helper-text and value-display in the canonical adjacent positions (linear: trailing; circular: centered inside the ring).
Both

Internationalisation

RTL · mirroring

Linear variant's fill direction follows the logical inline direction — fill grows from inline-start toward inline-end, which means visually right-to-left under RTL. CSS uses `inset-inline-start: 0` plus `inline-size: 60%` for the fill rather than `left: 0; width: 60%`. Circular variant's stroke direction is conventionally clockwise in LTR cultures; RTL implementations may keep clockwise (visually neutral, reads as "advancing") or flip to counterclockwise per locale convention. Document the chosen direction. Value-display number-formatting follows locale via `Intl.NumberFormat` (12.5% renders as 12,5 % in German). Accessible-name `aria-label` translates per locale.

Text expansion

Visible labels expand 30-50% under translation ("Loading" → "Wird geladen" 50% longer). Helper text expands similarly; reserve flexible inline- size in the helper-text slot. Value-display is stable across locales for percent values; non- percent contexts ("Step 3 of 7") expand ("Schritt 3 von 7" 30% longer) and may force the value-display below the bar at narrow widths. Long-running operations under expanded translations should favour the milestone-only announcement pattern over per-percent SR announcements.

Both

Variants, properties, states

Variants

Structurally different versions of the component.

linear circular

Properties

The same component, parameterised.

PropertyType
size sm | md | lg
determinate boolean
showValue boolean

States

Browser/user-driven (interactive) vs. app-driven (data).

KindStates
interactive
data
pendinginProgresscompleteerrorindeterminate
Both

State transitions

FromToTrigger
pendinginProgressConsumer increments value above 0
inProgresscompleteValue reaches max (100%)
inProgresserrorUnderlying task fails; consumer sets the error data state
inProgressindeterminateConsumer flips `determinate` from true to false (e.g., total size unknown mid-fetch)
Both

Figma↔Code mismatches

  1. 01
    Figma

    Inline "75%" text drawn as standalone label next to the bar

    Code

    `aria-valuenow="75"` on the root plus optional `<value-display>` slot

    Consequence

    Designers compose progress with a percentage label drawn next to the bar; developers wire the same number as `aria-valuenow` on the root and may double-render it as visible text. The two surfaces diverge on AT — designer's standalone text is invisible to the progressbar's accessible value; developer's `aria-valuenow` is the canonical signal. Without explicit anatomy, the visible-versus-AT split is unclear and implementations may ship the visible text alone without setting `aria-valuenow`.

    Correct

    Document the value-display slot as a visible mirror of the root's `aria-valuenow`. Figma carries the value text as a child of the progress instance, not a sibling. Code wires `aria-valuenow` on the root AND renders the value-display slot with `aria-hidden="true"` so the SR does not announce it twice.

  2. 02
    Figma

    Indeterminate drawn as decorative spinner without progress semantic

    Code

    `role="progressbar"` without `aria-valuenow` plus reduced-motion fallback

    Consequence

    Designers compose indeterminate progress as a generic spinner-loading-icon that may map to Skeleton, Toast, or a bare decorative SVG; developers ship `role="progressbar"` so AT announces "progressbar, busy". The two surfaces diverge on what the system actually means — designer's "loading icon" reads as decorative, developer's "busy progressbar" reads as a real task. Without explicit anatomy, indeterminate progress may ship without the role at all and AT users hear no busy signal.

    Correct

    Document indeterminate as a property of the progress component, not a separate decorative pattern. Figma carries an "indeterminate" variant of progress with the canonical animation and the `prefers-reduced-motion` fallback shape. Code ships `role="progressbar"` plus `aria-label`; omits `aria-valuenow` per APG; respects reduced-motion via CSS media query.

  3. 03
    Figma

    Circular ring drawn as decorative-only loading icon

    Code

    `role="progressbar"` plus accessible name plus aria-valuetext for non-percent contexts

    Consequence

    Designers ship circular progress as an icon called "Loader" or "Spinner" with no progress semantic in the design surface; developers wire it as `role="progressbar"` because the operation has measurable progress. The two surfaces diverge on whether the affordance is progress or decoration — invisible distinction in the design file lets implementations ship either pattern, and AT users get the wrong announcement.

    Correct

    Document circular as a structural variant of progress with the same role + accessible-name contract as linear. Figma carries Progress as one component with linear / circular variant property; "Spinner" components for purely decorative non-progress visuals are documented as a distinct pattern (or removed from the design system entirely if no real spinner- without-progress use-case exists).

  4. 04
    Figma

    Multi-step progress drawn as filled segments

    Code

    Single progressbar with `aria-valuetext: "Step 3 of 7"` (or escalation to Stepper)

    Consequence

    Designers compose multi-step progress as a row of segments where each filled segment represents one completed step (e.g., 3 of 7 segments filled); developers ship either a single progressbar with `aria-valuetext` reporting the step or a Stepper component. The two patterns have different semantics — progressbar is continuous, stepper is sequential with state. Without explicit anatomy, design files may communicate progressbar visual while developers ship Stepper anatomy (or vice versa).

    Correct

    Document the canonical decision rule — segments with no per-step state are progressbar with `aria-valuetext`; segments with per-step state (clickable, navigable, completed-vs-pending distinction) are Stepper. Figma carries two separate components and the design surface signals which semantic the implementation should adopt.

  5. 05
    Figma

    Progress drawn without label or value

    Code

    `aria-label="Upload progress"` (or `aria-labelledby`) required on root

    Consequence

    Designers ship icon-only progress (compact bar with no surrounding text) for visual cleanliness; developers must add `aria-label` so AT users hear what the progress represents. The two surfaces diverge on accessibility — sighted users see a bar with no context (still bad UX), AT users hear nothing at all without the label.

    Correct

    Document the accessible-name requirement as a non-negotiable contract. Figma carries an explicit label slot or documents the `aria-label` value as a component property (e.g., "Aria Label" property). Code rejects progress instances without `aria-label` or `aria-labelledby` — a build-time lint enforces the requirement.

Both

Contracts

Non-negotiable contracts

  1. WCAGWCAG 4.1.2 Name, Role, Value

    The progressbar element carries an accessible name via `aria-label` or `aria-labelledby`. Implementations that ship unlabeled progressbars violate WCAG 4.1.2 — AT users hear the percentage with no semantic context.

    Without a label, SR users hear "progressbar, 43 percent" with no indication of what is at 43 percent. The percentage alone communicates nothing — uploads, downloads, syncs, and form-saves all sound identical. The user cannot tell which task is progressing or whether multiple progressbars are reporting different operations.

  2. APGWAI-ARIA progressbar role + aria-valuenow guidance

    Determinate progress reports `aria-valuemin`, `aria-valuemax`, and `aria-valuenow`. Indeterminate progress OMITS `aria-valuenow` entirely (does not set it to 0 or the last known value).

    Without the omit-rule, indeterminate progress announces a stale value ("0 percent" or "43 percent") while the actual state is "we don't know". SR users mistrust the value or misread the operation as stuck. The omit rule is what canonical APG-compliant progressbars do; the absence of `aria-valuenow` is the busy-state signal.

  3. WCAGWCAG 2.3.3 Animation from Interactions

    Indeterminate animation respects `prefers-reduced-motion: reduce` — the canonical fallback is a static busy indicator (stripe pattern, single-frame pulse, or non-animated shape with `aria-valuetext="Working"`).

    Without the fallback, users with vestibular sensitivity or animation-fatigue experience sustained discomfort during long operations. WCAG 2.3.3 is AAA but the canonical contract treats it as required because the cost of compliance is one CSS media query.

  4. HTML specHTML standard — progress element semantics

    The progressbar element is non-interactive — no `tabindex`, no role conflicts. The component does not participate in tab-order and does not respond to keyboard or pointer activation.

    Without the rule, implementations may add `tabindex="0"` to draw focus on completion or `onClick` to act as cancel-buttons. Both patterns confuse keyboard users who encounter unexpected tab stops on what should be observation-only surfaces. Cancel affordances live as sibling elements, not on the progressbar itself.

  5. HTML specHTML standard — progress element semantics

    Native `<progress>` is an acceptable alternative implementation when no styling control is required. It carries the role + value semantics natively and respects `prefers-reduced-motion` automatically in browsers that animate the indeterminate state. Custom-styled `role="progressbar"` is preferred when the design system needs explicit control over the visual shape.

    Without documenting the alternative, implementations may unnecessarily reinvent the role-driven pattern when native `<progress>` would suffice. Native `<progress>` styling is limited (browser-specific pseudo-elements: `::-webkit-progress-bar`, `::-moz-progress-bar`); design systems with bespoke progress visuals choose `role="progressbar"`.

Vocabulary drift

WAI-ARIA
role=progressbar + aria-valuemin/max/now
Canonical ARIA pattern. WAI-ARIA Authoring Practices Guide documents the omit- `aria-valuenow`-for-indeterminate rule explicitly; the canonical anatomy mirrors this discipline.
HTML
<progress>
Native HTML element with `value` and `max` attributes; implies `role="progressbar"`. No native circular variant — design systems implement circular via SVG + `role="progressbar"`. Native `<progress>` animation for indeterminate state is browser-specific.
Material 3
LinearProgressIndicator + CircularProgressIndicator
Material 3 ships linear and circular as separate components rather than a single component with variants. Both carry the progressbar semantic; the canonical single-component-with-variants reflects the ARIA pattern more directly than the Material split.
Carbon
ProgressBar + InlineLoading + Loading
Carbon ships three patterns — `ProgressBar` (determinate linear), `InlineLoading` (status-text-with-spinner for inline contexts), `Loading` (full-page circular spinner). The canonical single-component covers all three via variant + status property.
Atlassian
ProgressBar + Spinner
Atlassian splits into ProgressBar (determinate linear) and Spinner (indeterminate circular). The canonical single-component with linear / circular variants plus determinate boolean covers both.
Polaris
ProgressBar + Spinner
Polaris similarly splits. ProgressBar is determinate linear; Spinner is indeterminate circular. Mobile-first Polaris guidance favours Spinner over ProgressBar for short operations and ProgressBar for long determinate ones.
React Aria
ProgressBar (with isIndeterminate)
React Aria ships a single ProgressBar primitive with `isIndeterminate` prop, matching the canonical single-component shape. Headless — consumers compose the visual track + fill themselves via the `useProgressBar` hook.
GitHub Primer
ProgressBar
Primer ships a linear-only ProgressBar with `progress` (0-100), `bg`, `barSize` props. No circular variant; Primer recommends external spinner libraries for indeterminate circular contexts.
Designer

Common mistakes

Blocker

#progress-no-label

Progress has no `aria-label` or `aria-labelledby`

Problem

The progressbar ships without an accessible name. SR users hear "progressbar, 43 percent" with no indication of what is at 43 percent — upload? download? form save? The percentage alone communicates nothing semantically. WCAG 4.1.2 Name-Role-Value violation.

Fix

Set `aria-label="Upload progress"` (or the task-specific name) on the root, or `aria-labelledby` pointing to a sibling label element's id. The label must name what is in progress — "Loading", "Working", or generic names pass the lint but fail the user. Translate the label per locale.

Blocker

#progress-indeterminate-with-valuenow

Indeterminate progress carries `aria-valuenow`

Problem

Indeterminate progress (no measurable percentage) ships with a stale `aria-valuenow` (often `0` or the last known value before the fetch became indeterminate). SR users hear "progressbar, 0 percent" or "43 percent" while the actual state is "we don't know". APG explicit rule violation.

Fix

Remove `aria-valuenow` entirely when `determinate=false`. Keep `aria-valuemin`, `aria-valuemax`, and the accessible name. SR announces "progressbar, busy" — the canonical indeterminate signal. Set or unset `aria-valuenow` on the same element as `determinate` flips; do not let the attribute lag.

Major

#progress-no-reduced-motion

Indeterminate animation does not honor `prefers-reduced-motion`

Problem

Indeterminate progress animates continuously (rotating ring, sliding bar, pulsing fill) under `prefers-reduced-motion: reduce`. Users with vestibular sensitivity or who fatigue from animation experience visual stress; on long operations the unstoppable motion becomes a real barrier. WCAG 2.3.3 Animation from Interactions violation.

Fix

Wrap the indeterminate animation in `@media (prefers-reduced-motion: reduce)` and fall back to a non-animated busy indicator — a static stripe pattern, a single-frame pulse, or simply the progress shape with `aria-valuetext="Working"` and no continuous motion. The fallback must still communicate "in progress" visually for sighted users.

Major

#progress-as-status-message

Progress component used for completion notification

Problem

The progress shape stays on screen after the task completes, sometimes with a checkmark replacing the fill, as a "complete" status message. SR users continue hearing "progressbar" semantics for what is now a static state — the role contradicts the meaning. Common in implementations that reuse the Progress component for both during-task and post-task surfaces.

Fix

On task completion, transition the surface to a different component — Toast for transient notification, `role="status"` text for inline confirmation, or `<output>` for form-result announcements. The Progress component's lifetime is during-task only; it unmounts or transitions out on completion. Implementations that show a "complete" checkmark inside progress for one second before transitioning are acceptable as long as the role-vs-meaning contradiction is brief.

Major

#progress-not-progressbar-role

Visual progress without `role="progressbar"`

Problem

The component renders a visible progress bar (or ring) but the underlying DOM is a plain `<div>` with no role. AT users hear nothing — no progressbar role, no value, no busy state. The affordance functions visually for sighted users and is invisible to everyone else.

Fix

Add `role="progressbar"` (or use native `<progress>`) plus `aria-valuemin`, `aria-valuemax`, `aria-valuenow` (determinate), `aria-label` or `aria-labelledby`. The canonical anatomy carries the role on the root slot; implementations must wire it.

Accessibility hints
Slot Accessibility hint
root `<div role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="60" aria-label="Upload progress">` (determinate) or `<div role="progressbar" aria-label="Loading">` without `aria-valuenow` (indeterminate). Accessible name is REQUIRED — `aria-label` or `aria-labelledby` pointing to a sibling label element. Native `<progress value="60" max="100">` is the alternate-implementation; both surfaces are canonical, the choice is implementation discretion.
track `aria-hidden="true"` is canonical. Track is visual scaffolding only; AT reads the progressbar's `aria-valuenow` via the root, not the SVG / DOM shapes. SVG implementations should mark inner `<circle>` / `<rect>` shapes with `aria-hidden` explicitly to prevent SR drift on raw shape announcements.
fill `aria-hidden="true"`. The fill is the visible progress; the AT semantic lives on the root. Indeterminate fill animates continuously when `prefers-reduced-motion` is `no-preference`; under `reduce`, the fill renders as a static busy indicator (e.g., a pulsing-once or a stripe- pattern) without continuous motion.
label Render as `<span id="progress-label">` and wire `<div role="progressbar" aria-labelledby="progress-label">`. The label and value-display together compose the SR announcement ("Uploading file 3 of 7, progressbar, 43 percent"). Avoid duplicating the label as both `aria-labelledby` and visible text (works) versus only `aria-label` (works but sighted users see no label) — pick one canonical pattern per design system.
value-display Decorative for AT (`aria-hidden="true"`) when the root's `aria-valuenow` reports the same number; the SR otherwise announces the value twice. When the visible text differs from the numeric value (e.g., "Step 3 of 7"), set the root's `aria-valuetext` to the same string and keep the slot decorative — `aria-valuetext` overrides `aria-valuenow` in the SR announcement.
status-icon `aria-hidden="true"`. The icon is visual confirmation of the terminal state; the AT semantic lives on the root via `aria-valuetext="Complete"` (or "Failed"). Do not give the icon its own role or accessible name — this duplicates the announcement and produces "Complete, image, complete" patterns on some SR / browser combinations.
helper-text Render as `<span id="progress-helper">` and wire `<div role="progressbar" aria-describedby="progress-helper">` so SR users hear the helper text alongside the value announcement. Avoid time-based predictions the user cannot verify ("2 minutes remaining" when the actual ETA fluctuates wildly is more confusing than no estimate).
announcement `<span aria-live="polite" class="sr-only">` whose text content updates at canonical milestones ("25 percent complete", "50 percent complete", ...). Use `aria-live="polite"` not "assertive" — progress is non-urgent; assertive interrupts SR users in mid-sentence. Long-running operations benefit from milestone announcements; short operations (under 5 seconds) usually do not.