Skip to content
Learn Measure Blog Case studies About
  • Home
  • All patterns
  • Component patterns

Loading Bar

Jun 1, 2022

This pattern shows how to build a color adaptive and accessible loading bar with the <progress> element.

Full article · Video on YouTube · Source on Github

<main id="loading-zone" aria-busy="true">
  <p>Loading Level</p>
  <div class="card">
    <label>
      <span class="sr-only">Loading progress</span>
      <progress 
        indeterminate 
        role="progressbar" 
        aria-describedby="loading-zone"
        tabindex="-1"
      >unknown</progress>
    </label>
  </div>
</main>
const progress = document.querySelector('progress')
const zone     = document.querySelector('#loading-zone')

const state = {
  val: .1
}

const roundDecimals = (val, places) =>
  +(Math.round(val + "e+" + places)  + "e-" + places)

const setProgress = () => {
  // set loading zone status
  zone.setAttribute('aria-busy', state.val < 1)

  // clear attributes if no value to show
  // <progress> will show indeterminate state
  if (state.val === null) {
    progress.removeAttribute('aria-valuenow')
    progress.removeAttribute('value')
    progress.focus()
    return
  }

  // round bad JS decimal math
  const val = roundDecimals(state.val, 2)
  const valPercent = val * 100 + "%"
  
  // set value for screenreaders and element values
  progress.value = val
  progress.setAttribute('aria-valuenow', valPercent)
  progress.innerText = valPercent

  // focus so screenreaders hear the announced value update
  progress.focus()
}
progress {
  --_track: hsl(228 100% 90%);
  --_track-size: min(10px, 1ex);
  --_progress: hsl(228 100% 50%);
  --_radius: 1e3px;
  --_indeterminate-track: linear-gradient(to right,
    var(--_track) 45%,
    var(--_progress) 0%,
    var(--_progress) 55%,
    var(--_track) 0%
  );
  --_indeterminate-track-size: 225% 100%;
  --_indeterminate-track-animation: progress-loading 2s infinite ease;
  
  /*  reset  */
  appearance: none;
  border: none;
  
  /*  custom style  */
  position: relative;
  height: var(--_track-size);
  border-radius: var(--_radius);
  overflow: hidden;

  @media (prefers-color-scheme: dark) {
    --_track: hsl(228 20% 30%);
    --_progress: hsl(228 100% 75%);
  }

  &:focus-visible {
    outline-color: var(--_progress);
  }
  
  /*  Safari/Chromium  */
  &[value]::-webkit-progress-bar {
    background-color: var(--_track);
  }
  
  &[value]::-webkit-progress-value {
    background-color: var(--_progress);
    transition: inline-size .25s ease-out;
  }
  
  /*  Firefox  */
  &[value]::-moz-progress-bar {
    background-color: var(--_progress);
  }
  
  /*  indeterminate  */
  &:indeterminate::after {
    content: "";
    inset: 0;
    position: absolute;
    background: var(--_indeterminate-track);
    background-size: var(--_indeterminate-track-size);
    background-position: right; 
    animation: var(--_indeterminate-track-animation);
  }
  
  /*  indeterminate Safari  */
  &:indeterminate::-webkit-progress-bar {
    background: var(--_indeterminate-track);
    background-size: var(--_indeterminate-track-size);
    background-position: right; 
    animation: var(--_indeterminate-track-animation);
  }
  
  /*  indeterminate Firefox  */
  &:indeterminate::-moz-progress-bar {
    background: var(--_indeterminate-track);
    background-size: var(--_indeterminate-track-size);
    background-position: right; 
    animation: var(--_indeterminate-track-animation);
  }
  
  /*  complete  */
  &:not([max])[value="1"]::before,
  &[max="100"][value="100"]::before {
    content: "✓";
    
    position: absolute;
    inset-block: 0;
    inset-inline: auto 0;
    display: flex;
    align-items: center;
    padding-inline-end: max(calc(var(--_track-size) / 4), 3px);

    color: white;
    font-size: calc(var(--_track-size) / 1.25);
  }
}

@keyframes progress-loading {
  50% {
    background-position: left; 
  }
}
Open demo
Last updated: Jun 1, 2022 — Improve article
Share
subscribe

Contribute

  • File a bug
  • View source

Related content

  • developer.chrome.com
  • Chrome updates
  • Web Fundamentals
  • Case studies
  • Podcasts
  • Shows

Connect

  • Twitter
  • YouTube
  • Google Developers
  • Chrome
  • Firebase
  • Google Cloud Platform
  • All products
  • Terms & Privacy
  • Community Guidelines

Except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 4.0 License, and code samples are licensed under the Apache 2.0 License. For details, see the Google Developers Site Policies.