Progress
When to use it
Section titled “When to use it”From the Lumo.Web.Component.V1.0.Complete library — theUse progress to show how far along a measurable thing is — a score against a target, an initiative’s completion, a multi-step flow.
progress description, read 2026-10-19.Where it goes
Section titled “Where it goes”- in a card body beneath a metric, and in a kanban card
When not to use it
Section titled “When not to use it”- Don’t use it for an unknown wait; use Spinner instead.
- Use instead:
Spinner— not implemented in this library yet.
- Use instead:
What each variant is for
Section titled “What each variant is for”| Property | Value | What it is for |
|---|---|---|
status |
brand |
not stated in the source |
status |
positive |
not stated in the source |
status |
warning |
not stated in the source |
status |
negative |
not stated in the source |
size |
sm |
not stated in the source |
size |
md |
not stated in the source |
Accessibility
Section titled “Accessibility”- role=“progressbar” sits on the track, not the wrapper: a progressbar’s contents are not exposed to assistive tech, so putting it on the wrapper would swallow the label and the value text
src/components/progress/progress.jsx:87 - carries aria-valuenow, aria-valuemin and aria-valuemax, with the percent clamped to 0-100
src/components/progress/progress.jsx:95 - named by reference via aria-labelledby when a label is shown, and by aria-label when it is not, so the bar is never unnamed
src/components/progress/progress.jsx:99
Composition
Section titled “Composition”Nothing in the stories shows this component composed with another.
What this version promises
Section titled “What this version promises”See Versioning. At 0.1.0 the public surface is the named exports of src/index.ts; this component is one of them.
This component’s release verdict is Cleared and its Development status reads Released. A Cleared verdict is a recommendation with reasons, recorded in its release review — a human decides whether a release happens, and no agent approves its own work.
Every story that is not a variant-matrix row, embedded live from the deployed Storybook in both themes. The matrix itself is on the Design tab.
Label off
Section titled “Label off”Value off
Section titled “Value off”Label and Value off
Section titled “Label and Value off”Value text — not a percentage
Section titled “Value text — not a percentage”Fill — 0, 50 and 100
Section titled “Fill — 0, 50 and 100”Fluid width
Section titled “Fluid width”In context
Section titled “In context”Import
Section titled “Import”import { Progress } from '@ds-house/lumo-design-system';| Prop | Type | Default | Notes |
|---|---|---|---|
status |
`‘brand’ | ‘positive’ | ‘warning’ |
size |
`‘sm’ | ‘md’` | 'sm' |
label |
boolean |
true |
Figma Label. Shows the label line above the bar. |
labelText |
string |
'Commercial strategy' |
Figma Label text. Names what is progressing. Still used as the bar’s accessible name when label is false, so it is never pointless to set. |
value |
boolean |
true |
Figma Value. Shows the value text beside the bar. |
valueText |
string |
— | Figma Value text. Defaults to percent rendered as a percentage. |
percent |
number |
72 |
How far along, 0–100. Code-only — Figma draws the fill at a fixed width and has no property for it. See docs/naming-conflicts.md and docs/progress.md. |
className |
string |
'' |
Union types
Section titled “Union types”status:'brand'|'positive'|'warning'|'negative'size:'sm'|'md'
Tokens it needs
Section titled “Tokens it needs”| Token | Light | Dark |
|---|---|---|
--body-body3 |
not in the build | — |
--border-radius-full |
not in the build | — |
--border-width-default |
not in the build | — |
--color-bg-negative-idle |
not in the build | — |
--color-bg-positive-idle |
not in the build | — |
--color-bg-primary-idle |
not in the build | — |
--color-bg-surfacesecondary |
not in the build | — |
--color-bg-warning-idle |
not in the build | — |
--color-text-primary |
not in the build | — |
--color-text-secondary |
not in the build | — |
--label-label1 |
not in the build | — |
--spacing-2 |
not in the build | — |
--spacing-4 |
not in the build | — |
--spacing-8 |
not in the build | — |
--spacing-gap-horizontal-sm |
not in the build | — |
--spacing-gap-xs |
not in the build | — |
A dash under Dark means the token does not change between themes.
Storybook’s own props table
Section titled “Storybook’s own props table”The Figma node
Section titled “The Figma node”The variant matrix
Section titled “The variant matrix”Each row as the set draws it, in both themes.
Status=brand, Size=sm
Section titled “Status=brand, Size=sm”Status=brand, Size=md
Section titled “Status=brand, Size=md”Status=positive, Size=sm
Section titled “Status=positive, Size=sm”Status=positive, Size=md
Section titled “Status=positive, Size=md”Status=warning, Size=sm
Section titled “Status=warning, Size=sm”Status=warning, Size=md
Section titled “Status=warning, Size=md”Status=negative, Size=sm
Section titled “Status=negative, Size=sm”Status=negative, Size=md
Section titled “Status=negative, Size=md”All eight variants
Section titled “All eight variants”What Figma never bound, and the gaps recorded against it
Section titled “What Figma never bound, and the gaps recorded against it”— reported, not filled
Gap 1 — Size is the whole point of the property and neither value is bound
Section titled “Gap 1 — Size is the whole point of the property and neither value is bound”The node draws the track 6px tall at sm and 8px at md, both as raw numbers with no
variable behind them. There is no token near those values anywhere in the system: the
core size scale starts at 12, and the semantic size families are icon, control,
avatar, touchTarget and layout — none of which means “the height of a progress
track”.
Snapping to the nearest step in a plausible family, the way Badge did with its unbound
10px padding, is not available here: the nearest step to 6 is 4, which would render
sm a third thinner than drawn and make the two sizes read as 4/8 rather than 6/8 —
and the height is the only thing Size changes.
What the code does instead: composes the two heights from core spacing primitives,
exactly — calc(var(--spacing-4) + var(--spacing-2)) and var(--spacing-8). That
reaches past the semantic layer, which a component should not do. Both are isolated in
--lumo-progress-track-height, one declaration per size, so closing the gap is a
one-line change each.
What would close it: a semantic size family for the bar — size/progress/sm = 6
and size/progress/md = 8, or whatever the designer prefers to call it — with the
node’s Track frame bound to it.
Gap 2 — the track radius binds a core token, skipping the semantic layer
Section titled “Gap 2 — the track radius binds a core token, skipping the semantic layer”Track and Indicator are both bound to core/border/radius/full, not to a semantic
radius. The semantic radii that resolve to the same 999 are border/radius/chip and
border/radius/avatar; a progress track is neither, so borrowing one of those names
would be a lie about meaning dressed up as a fix.
What the code does instead: reproduces the binding the node actually has,
var(--border-radius-full), isolated as --lumo-progress-radius.
What would close it: a border/radius/progress (or a shared border/radius/pill)
semantic alias, with the node bound to that.
Gap 3 — the fill cannot animate, because the easings have no value
Section titled “Gap 3 — the fill cannot animate, because the easings have no value”A bar whose fill moves wants a transition, and a transition needs a duration and an
easing. The duration tokens exist. All five easing tokens —
motion-easing-standard, -decelerate, -accelerate, -emphasized and -linear —
are declared in the Figma export with no value in any mode, so build-tokens.js
skips them and emits no --motion-easing-* custom property at all. The token build
says so on every run.
The node binds no easing, so nothing was dropped from the design. But Progress is the component in this system most likely to want one, and it cannot have it.
What the code does instead: nothing. The fill snaps. A substituted cubic-bezier
would be a design decision taken by an engineer, and a CSS keyword such as ease
presented as the bound value would be worse — it would look resolved.
What would close it: give the five easings values in Figma and re-export. One
transition declaration on .lumo-progress__indicator then covers it.
Gap 4 — the node’s fill does not agree with its own value text
Section titled “Gap 4 — the node’s fill does not agree with its own value text”Every variant draws Indicator at a fixed 158px while the value text reads
72%. The track in that layout is ~246px wide, so the drawn fill is about 64%.
The two cannot both be right.
This is not fillable as a token, and it is not really a bug in the design either — a static Figma frame has no way to express “the fill follows the number”. It is recorded because QA will measure it.
What the code does instead: drives the fill from percent, so 72% renders at 72%
(177px of 246px in the web mode at 280px wide). The coded component will therefore
not match a pixel measurement of the node’s indicator, and that is correct.
Where Figma and this repo disagree across the system, see the disagreements record.
Every commit touching this component and its subcomponents — a change to a subcomponent changes the component, and a log that reads only the parent file misses it.
| Commit | Date | Shipped in | Subject |
|---|---|---|---|
2b94809 |
2026-10-19 | unknown | Write the ten component intent files |
c8bc699 |
2026-10-18 | unknown | Add the test suite, and retire the deliberate stop |
9525e0f |
2026-10-17 | unknown | Make the repo a publishable npm package |
d64f7e7 |
2026-10-10 | unknown | Build Progress from Figma 69:1151 — eight variants, four gaps reported |
7ccfbbb53757.