Menu


Progress bar


A track with a fill showing how far something has got, with an optional label and value above it; it can say the work failed, or that its end is not known.

Customize the preview


Choices
Show
View
Uploading 3 of 4 files75%

Options


Progress bar options
ChoiceValuesDefaultWhat it does
tonesmbk-progress--tone-<value>defaultbrandsuccesswarningdangerbrand
What fills the track. A failed bar is always danger.
sizesmbk-progress--size-<value>mdsmmd
Small uses the slim track and the smaller label styles, for dense rows and lists.
typesmbk-progress--type-<value>defaultpixeldefault
Default is one smooth bar. Pixel cuts the track into twenty segments and fills whole ones.
statesmbk-progress--state-<value>defaultfailedunknowndefault
Failed leads with the danger icon and words and offers to try again. Unknown shows a detached part of the track and no number, for work whose end is not known.

Partssmbk-progress__headersmbk-progress__labelsmbk-progress__iconsmbk-progress__valuesmbk-progress__retrysmbk-progress__tracksmbk-progress__fill

Examples


Usage

Label, percentage and the track.

Uploading 3 of 4 files75%

Tones

Five fills.

Profile completion75%
Storage used40%
Backup complete100%
Monthly quota90%
Disk almost full97%

Small

The slim track, smaller text.

Syncing75%

Failed

Leads with the danger icon and words, the fill in danger where it stopped, and offers to try again.

Upload failed
Sync failed

Unknown

Without a value: a detached part of the track and no number, for work whose end is not known.

Preparing the export…
Working…

Pixel

Twenty segments, filled whole; known and unknown, in every tone.

Rendering frames75%
Frames rendered100%
Waiting for the render queue

Guidelines


Purpose

Shows how far an amount of work has got: an upload, a multi-step form, a quota. The fill grows along a thin track; a label above says what is happening and, optionally, the percentage. It can also say that the work failed, or that it is going on with no known end.

When to use

  • Any wait with a known end: uploads, downloads, steps.
  • A quota or a capacity, as a static reading.
  • A wait whose end is not known, with the unknown state, when the work takes long enough to need a word about it.

When not to use

  • A skeleton's job: holding a layout while content loads.

Usage rules

Do

  • Give every progress bar a label, even when hidden, so the role has a name.
  • Use the small size in dense rows and lists.
  • Write a failed label that names what failed, such as "Upload failed".

Don't

  • Use the warning tone to mean "nearly full" without saying so in the label. Color must not be the only cue (WCAG 1.4.1 Use of Color).
Storage almost full90%
Do

Say what the warning is about in the label, so color is not the only cue.

Storage90%
Don't

A warning color with a neutral label leaves readers who cannot see the color without the warning.

Anatomy

  • The block, a column: the header row, then the track.
  • The header part holds the label part and the value part, at either end. When the work failed, the label leads with the icon part, a crossed circle, and the retry part, a "Try again" button underlined like a link, takes the value's place.
  • The track part, a thin bar in the subtle track color, pressed into the surface, slimmer at the small size.
  • The fill part, the colored bar laid over it from the start, as wide as the progress and as tall as the track. When the end is not known, it is a detached part from three tenths of the track to seven.
  • In the pixel type, the track and the fill are cut into twenty square segments, and the fill shows in whole ones.

Behavior

The fill's width is data, set by the wrapper from the value. When the value changes, the fill grows or shrinks to it with the state motion, so the change can be followed; under reduced motion it jumps. The pixel type rounds the value to whole segments, each a twentieth. With showValue the label row shows the percentage, or the value in words when aria-valuetext gives them, as the meter does.

Leave the value out and the bar takes the unknown state: it shows no number, and its detached fill stays still. A failed bar keeps its fill where the work stopped, in the danger color whatever its tone, and its "Try again" calls onRetry; without one it offers no retry.

Accessibility


What it does for you

  • The track carries the progress-bar role with its minimum, maximum and current value; the label is its accessible name. The component puts the role, the name and aria-valuetext on the track.
  • Assistive technology reads the value from the role, so the visible percentage is a convenience, not the source. A value above the maximum is read as the maximum.
  • An unknown bar has no current value, which tells assistive technology its end is not known.
  • A failed bar says so in words, its label, not by its color alone. Its retry is a real button, at least 24 pixels square to press.
  • In development the wrapper warns in the console when a bar has no name.
  • In high contrast mode the empty track is outlined and the done part filled with the highlight color.

What you must do

  • Give a bar with no visible label an aria-label, or aria-labelledby pointing at words elsewhere on the page.
  • Give aria-valuetext when the value reads better in words.
  • Write a failed label that names what failed, such as "Upload failed".
  • Say in the label how a bar stands when its tone means something, such as "nearly full". Color must not be the only cue (WCAG 1.4.1 Use of Color).

How to test

  • With a screen reader, it is read as a progress bar by its name, with its value. An unknown bar is read with no value.
  • With a failed bar, the label says what failed, and Tab reaches "Try again", which Enter or Space presses.
  • With text at 200% and the page 320 pixels wide, the label row stays whole and the bar fits its width.
  • In a forced-color mode, the empty track is outlined and the done part is drawn in the highlight color.

Keyboard

Progress bar keys
KeyWhat it does
Tab
On a failed bar, moves focus to "Try again". The bar itself takes no focus.
EnterorSpace
On "Try again", presses it.

Source


The files the progress bar is made of. Its folder on GitHub (opens in a new tab) holds them with its wrapper and its document.

Stylesheet
progress.css
/* smbk-progress — Progress bar. Figma "Progress Bar" 4168:13627 and "ProgressTrack" 4168:13602.
 * The track and the fill are the same height on the same line: the empty track in the subtle track
 * color, pressed in, and the fill laid over it from the start edge, clipped by its round ends.
 * The fill's width is set inline as a percentage by the wrapper; that is data, not a design value. */

.smbk-progress {
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: var(--smbk-space-stack-sm);
  width: 100%;
}

.smbk-progress__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--smbk-space-inline-md);
}

.smbk-progress__label {
  display: inline-flex;
  align-items: center;
  gap: var(--smbk-space-inline-xs);
  min-width: 0;
  color: var(--smbk-color-text-default);
  font-family: var(--smbk-typography-body-sm-font-family);
  font-size: var(--smbk-typography-body-sm-font-size);
  font-weight: var(--smbk-typography-body-sm-font-weight);
  line-height: var(--smbk-typography-body-sm-line-height);
  letter-spacing: var(--smbk-typography-body-sm-letter-spacing);
}

.smbk-progress__icon {
  flex-shrink: 0;
  color: var(--smbk-color-feedback-danger-icon);
  font-size: var(--smbk-size-icon-sm);
}

.smbk-progress__value {
  color: var(--smbk-color-text-subtle);
  font-family: var(--smbk-typography-label-md-font-family);
  font-size: var(--smbk-typography-label-md-font-size);
  font-weight: var(--smbk-typography-label-md-font-weight);
  line-height: var(--smbk-typography-label-md-line-height);
  letter-spacing: var(--smbk-typography-label-md-letter-spacing);
  text-transform: var(--smbk-typography-label-md-text-transform);
  font-variant-numeric: tabular-nums;
}

/* Try again: words underlined like a link, in a box at least big enough to press, the small press box shared in
   press-box/press-box.css, with its focus ring and its press area on a touch screen. */
.smbk-progress__retry {
  min-width: var(--smbk-size-target-min);
  min-height: var(--smbk-size-target-min);
  margin-block: calc((var(--smbk-typography-label-md-font-size) * var(--smbk-typography-label-md-line-height) - var(--smbk-size-target-min)) / 2);
  border-radius: var(--smbk-radius-control-xs);
  color: var(--smbk-color-feedback-danger-text-on-subtle);
  font-family: var(--smbk-typography-label-md-font-family);
  font-size: var(--smbk-typography-label-md-font-size);
  font-weight: var(--smbk-typography-label-md-font-weight);
  line-height: var(--smbk-typography-label-md-line-height);
  letter-spacing: var(--smbk-typography-label-md-letter-spacing);
  text-decoration: underline;
}

.smbk-progress__track {
  box-sizing: border-box;
  position: relative;
  display: flex;
  overflow: hidden;
  width: 100%;
  height: var(--smbk-space-inset-xs);
  border-radius: var(--smbk-radius-full);
  background: var(--smbk-color-surface-track-subtle);
  box-shadow: var(--smbk-elevation-inset);
}

.smbk-progress__fill {
  position: relative;
  display: block;
  height: 100%;
  border-radius: var(--smbk-radius-full);
  background: var(--smbk-color-surface-brand);
}

/* ---- tone ---- */

.smbk-progress--tone-default .smbk-progress__fill { background: var(--smbk-color-surface-inverse); }
.smbk-progress--tone-success .smbk-progress__fill { background: var(--smbk-color-feedback-success-surface); }
.smbk-progress--tone-warning .smbk-progress__fill { background: var(--smbk-color-feedback-warning-surface); }
.smbk-progress--tone-danger .smbk-progress__fill { background: var(--smbk-color-feedback-danger-surface); }

/* ---- state ---- */

/* Failed: the words and the icon in danger, the fill too, whatever the tone. */
.smbk-progress--state-failed .smbk-progress__label { color: var(--smbk-color-feedback-danger-text-on-subtle); }
.smbk-progress--state-failed .smbk-progress__fill { background: var(--smbk-color-feedback-danger-surface); }

/* Unknown: a detached part of the track, from three tenths to seven, plainly not a measured length. */
.smbk-progress--state-unknown .smbk-progress__fill {
  width: 40%;
  margin-inline-start: 30%;
}

/* ---- small: the slim track, smaller text ---- */

.smbk-progress--size-sm .smbk-progress__track {
  height: var(--smbk-space-inset-2xs);
}

.smbk-progress--size-sm .smbk-progress__label {
  font-family: var(--smbk-typography-body-xs-font-family);
  font-size: var(--smbk-typography-body-xs-font-size);
  font-weight: var(--smbk-typography-body-xs-font-weight);
  line-height: var(--smbk-typography-body-xs-line-height);
  letter-spacing: var(--smbk-typography-body-xs-letter-spacing);
}

.smbk-progress--size-sm .smbk-progress__icon {
  font-size: var(--smbk-size-icon-xs);
}

.smbk-progress--size-sm .smbk-progress__value,
.smbk-progress--size-sm .smbk-progress__retry {
  font-family: var(--smbk-typography-label-sm-font-family);
  font-size: var(--smbk-typography-label-sm-font-size);
  font-weight: var(--smbk-typography-label-sm-font-weight);
  line-height: var(--smbk-typography-label-sm-line-height);
  letter-spacing: var(--smbk-typography-label-sm-letter-spacing);
}

.smbk-progress--size-sm .smbk-progress__retry {
  margin-block: calc((var(--smbk-typography-label-sm-font-size) * var(--smbk-typography-label-sm-line-height) - var(--smbk-size-target-min)) / 2);
}

/* ---- pixel: the track cut into twenty square segments a hairline-pair apart, the fill showing
   through whole ones. A mask over the track does the cutting, so the segments are the same in the
   empty track and the fill, and the wrapper sends a value in whole segments (twentieths). The mask's
   color is never seen: only its alpha cuts. ---- */

.smbk-progress--type-pixel .smbk-progress__track {
  border-radius: 0;
  box-shadow: none;
  mask-image: linear-gradient(to right, currentcolor calc(100% - var(--smbk-space-2)), transparent 0);
  mask-size: calc((100% + var(--smbk-space-2)) / 20) 100%;
  mask-repeat: repeat-x;
}

.smbk-progress--type-pixel .smbk-progress__fill {
  border-radius: 0;
}

/* ---- motion: when the value changes, the fill grows or shrinks to it with the state purpose,
   so the change can be followed. No movement under reduced motion. ---- */

.smbk-progress__fill {
  transition: width var(--smbk-motion-state-duration) var(--smbk-motion-state-easing);
}

@media (prefers-reduced-motion: reduce) {
  .smbk-progress__fill {
    transition: none;
  }
}

/* ---- touch: the retry action's invisible press area (press-box/press-box.css) reaches over the bar under it ---- */

@media (pointer: coarse) {
  .smbk-progress__retry::after {
    z-index: 1;
  }
}

/* ---- high contrast (forced colors) ---- */
/* In Windows high-contrast mode the browser drops fills and shadows; this puts the meaning back with the
   user's own colors. Nothing here applies outside that mode. */

@media (forced-colors: active) {
  /* The empty track outlined with a hairline inside it, the done part filled with Highlight. */
  .smbk-progress__track {
    outline: var(--smbk-border-width-hairline) solid CanvasText;
    outline-offset: calc(-1 * var(--smbk-border-width-hairline));
  }

  .smbk-progress .smbk-progress__fill {
    background: Highlight;
  }
}
Tokens it reads
Progress bar tokens
Token and CSSValuePreviewFoundation
focus.ring.width--smbk-focus-ring-width2pxFocus
color.border.focus--smbk-color-border-focuslight: #cc0d0ddark: #f34336changes with themeColor
focus.ring.offset--smbk-focus-ring-offset2pxFocus
size.target.comfortable--smbk-size-target-comfortable44pxSize
space.stack.sm--smbk-space-stack-smcomfortable: 8pxcompact: 6pxchanges with densitySpacing
space.inline.md--smbk-space-inline-mdcomfortable: 8pxcompact: 6pxchanges with densitySpacing
space.inline.xs--smbk-space-inline-xs4pxSpacing
color.text.default--smbk-color-text-defaultlight: #232527dark: #e7ebf0changes with themeColor
typography.body-sm--smbk-typography-body-sm-font-family--smbk-typography-body-sm-font-size--smbk-typography-body-sm-font-weight--smbk-typography-body-sm-line-height--smbk-typography-body-sm-letter-spacing--smbk-typography-body-sm-text-transformfontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 14pxfontWeight: 400lineHeight: 1.5letterSpacing: 0px

The quick brown fox

Typography
color.feedback.danger.icon--smbk-color-feedback-danger-iconlight: #cc0d0ddark: #fe7d6dchanges with themeColor
size.icon.sm--smbk-size-icon-sm16pxSize
color.text.subtle--smbk-color-text-subtlelight: #4e555edark: #9ba5b1changes with themeColor
typography.label-md--smbk-typography-label-md-font-family--smbk-typography-label-md-font-size--smbk-typography-label-md-font-weight--smbk-typography-label-md-line-height--smbk-typography-label-md-letter-spacing--smbk-typography-label-md-text-transformfontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 14pxfontWeight: 500lineHeight: 1.4letterSpacing: 0px

The quick brown fox

Typography
size.target.min--smbk-size-target-min24pxSize
radius.control.xs--smbk-radius-control-xs2pxRadius
color.feedback.danger.text-on-subtle--smbk-color-feedback-danger-text-on-subtlelight: #a51d17dark: #febdb2changes with themeColor
space.inset.xs--smbk-space-inset-xs4pxSpacing
radius.full--smbk-radius-full9999pxRadius
color.surface.track-subtle--smbk-color-surface-track-subtlelight: #c4cdd8dark: #2d3237changes with themeColor
elevation.inset--smbk-elevation-insetlight: inset 0px 0px 1px 0px #373d434ddark: inset 0px 0px 1px 0px #00000066Elevation
color.surface.brand--smbk-color-surface-brand#cc0d0dColor
color.surface.inverse--smbk-color-surface-inverselight: #2d3237dark: #e7ebf0changes with themeColor
color.feedback.success.surface--smbk-color-feedback-success-surface#037a1fColor
color.feedback.warning.surface--smbk-color-feedback-warning-surface#b14904Color
color.feedback.danger.surface--smbk-color-feedback-danger-surface#cc0d0dColor
space.inset.2xs--smbk-space-inset-2xs2pxSpacing
typography.body-xs--smbk-typography-body-xs-font-family--smbk-typography-body-xs-font-size--smbk-typography-body-xs-font-weight--smbk-typography-body-xs-line-height--smbk-typography-body-xs-letter-spacing--smbk-typography-body-xs-text-transformfontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 12pxfontWeight: 400lineHeight: 1.5letterSpacing: 0px

The quick brown fox

Typography
size.icon.xs--smbk-size-icon-xs12pxSize
typography.label-sm--smbk-typography-label-sm-font-family--smbk-typography-label-sm-font-size--smbk-typography-label-sm-font-weight--smbk-typography-label-sm-line-height--smbk-typography-label-sm-letter-spacing--smbk-typography-label-sm-text-transformfontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 12pxfontWeight: 500lineHeight: 1.4letterSpacing: 0px

The quick brown fox

Typography
space.2--smbk-space-22pxSpacing
motion.state.duration--smbk-motion-state-duration200msMotion
motion.state.easing--smbk-motion-state-easingcubic-bezier(0.65, 0, 0.35, 1)Motion
border-width.hairline--smbk-border-width-hairline1pxBorder width
Manifest
progress.manifest.json
{
  "name": "progress",
  "block": "smbk-progress",
  "summary": "A track with a fill showing how far something has got, with an optional label and value above it; it can say the work failed, or that its end is not known.",
  "adapt": "css",
  "axes": {
    "tone": {
      "values": [
        "default",
        "brand",
        "success",
        "warning",
        "danger"
      ],
      "default": "brand",
      "description": "What fills the track. A failed bar is always danger."
    },
    "size": {
      "values": [
        "md",
        "sm"
      ],
      "default": "md",
      "description": "Small uses the slim track and the smaller label styles, for dense rows and lists."
    },
    "type": {
      "values": [
        "default",
        "pixel"
      ],
      "default": "default",
      "description": "Default is one smooth bar. Pixel cuts the track into twenty segments and fills whole ones."
    },
    "state": {
      "values": [
        "default",
        "failed",
        "unknown"
      ],
      "default": "default",
      "description": "Failed leads with the danger icon and words and offers to try again. Unknown shows a detached part of the track and no number, for work whose end is not known."
    }
  },
  "flags": {},
  "parts": [
    "header",
    "label",
    "icon",
    "value",
    "retry",
    "track",
    "fill"
  ],
  "figmaNodeId": "4168:13627"
}