Progress Indicators

Purpose

Linear trackers represent progress as the accumulation over time using a specific unit of measurement, such as dollars or months. To track progress measured in discrete units, use a segmented tracker. To track uninterrupted progress towards an end goal, use a continuous linear tracker.

Progress Bars

Segmented Linear Tracker


Example - Segmented Linear Tracker

This tracker shows the progress of monthly payments, with each segment representing a month.

<div class="linear-tracker-segmented" id="demo-seg-linear">

  <div role="progressbar" aria-valuemin="0" aria-valuemax="100"
       aria-valuenow="0" aria-label="January payment" class="progress-bar">
    <div class="progress-track count-width"></div>
  </div>

  <div role="progressbar" aria-valuemin="0" aria-valuemax="100"
       aria-valuenow="0" aria-label="February payment" class="progress-bar">
    <div class="progress-track count-width"></div>
  </div>

  <div role="progressbar" aria-valuemin="0" aria-valuemax="100"
       aria-valuenow="0" aria-label="March payment" class="progress-bar">
    <div class="progress-track count-width"></div>
  </div>

  <div role="progressbar" aria-valuemin="0" aria-valuemax="100"
       aria-valuenow="0" aria-label="April payment" class="progress-bar">
    <div class="progress-track count-width"></div>
  </div>

  <div role="progressbar" aria-valuemin="0" aria-valuemax="100"
       aria-valuenow="0" aria-label="May payment" class="progress-bar">
    <div class="progress-track count-width"></div>
  </div>

  <div role="progressbar" aria-valuemin="0" aria-valuemax="100"
       aria-valuenow="0" aria-label="June payment" class="progress-bar">
    <div class="progress-track count-width"></div>
  </div>
</div>

<div id="seg-linear-live" aria-live="polite" class="flex flex-justify-end legal-1 linear-tracker-label"></div>

<script>
  const segLinearAlert = document.getElementById('seg-linear-live');

  function demoSegLinear() {
    let tracker = document.getElementById('demo-seg-linear');
    if(!tracker) {
      window.clearInterval(segLinearInterval);
      return;
    }
    let progressBarList = tracker.querySelectorAll('.progress-bar');

    let i = 0;
    let interval = setInterval(function() {
      progressBarList[i].setAttribute('aria-valuenow', '100');
      if(segLinearAlert) {
        segLinearAlert.innerHTML = progressBarList[i].getAttribute('aria-label') + ' complete';
      }
      i++;
      if(i === progressBarList.length) clearInterval(interval);
    }, 3000);

    setTimeout(function() {
      progressBarList.forEach(function(progBar) {
        progBar.setAttribute('aria-valuenow', '0');
        if(segLinearAlert) {
          segLinearAlert.innerHTML = progBar.getAttribute('aria-valuenow') + '%';
        }
      })
    }, 21000)
  };

  demoSegLinear();
  const segLinearInterval = window.setInterval(demoSegLinear, 22000);
</script>

Continuous Linear Tracker


Example - Continuous Linear Tracker

This tracker shows the customers progress as they accumulate miles in pursuit of a defined goal. The color of the tracker can vary depending on the brand.

<div class="linear-tracker margin-1-t" id="demo-cont-linear">
  <div class="tracker-alert" role="alert"></div>
  <div 
    role="progressbar" 
    aria-valuemin="0" 
    aria-valuemax="20000"
    aria-valuenow="0"
    aria-label="Miles 0 from 20000"
    aria-valuetext="0 miles"
    data-percent="0"
    class="progress-bar"
  >
    <div class="progress-track count-width">
      <i class="glyph" data-dls-glyph="check" data-dls-glyph-size="md" data-dls-icon-role="decorative"></i>
    </div>
  </div>
  <p class="legal-2 flex flex-justify-between pad-1-t">
    <span id="left-label">0 miles</span>
    <span id="right-label">
      <span id="right-label-current-value">
        <span id="current-value" aria-hidden="true"></span>
        <span class="sr-only" id="sr-only-current-value" aria-live="polite"></span>
      </span>
      of
      <span id="right-label-max-value"></span>
      miles
    </span>
  </p>
</div>

<script>
  const rightLabelCurrent = document.getElementById('right-label-current-value');
  const rightLabelMax = document.getElementById('right-label-max-value');
  const trackerAlert = document.querySelector('.tracker-alert');

  const maxMiles = 20000;

  rightLabelMax.innerText = maxMiles.toLocaleString('en-US');

  function demoContLinear() {
    let tracker = document.getElementById('demo-cont-linear');
    if(!tracker) {
      window.clearInterval(contLinearInterval);
      return;
    }
    let progressBar = tracker.querySelector('.progress-bar');
    let value = Number(progressBar.getAttribute('aria-valuenow'));
    const maxValue = Number(progressBar.getAttribute('aria-valuemax'));
    const minValue = Number(progressBar.getAttribute('aria-valuemin'));

    // reset alert content so alert doesn't get read before completion
    trackerAlert.innerHTML = "";

    if(value >= maxValue) {
      value = 0;
      tracker.classList.remove('complete');
    } else {
      value += 2000;
      // add content into the alert element to trigger the screen reader to read alert
      if(value === maxValue){
        tracker.classList.add('complete');
        trackerAlert.innerHTML = '<p class="body-1 dls-gray-06 pad-1-b">You\'ve successfully earned back your miles!</p>';
      }
    }

    progressBar.setAttribute('aria-valuenow', value);

    // provide more context to the current value by including units
    progressBar.setAttribute('aria-valuetext', `${value} miles`);

    if(rightLabelCurrent) {
      // adds commas to the number ex: 2000 --> 2,000
      const formattedCurrentValue = value.toLocaleString('en-US');
      // visually, only show current number
      document.querySelector("span#current-value").innerHTML = formattedCurrentValue;
      // include units for screen reades
      document.querySelector("span#sr-only-current-value").innerHTML = `${formattedCurrentValue} miles`;
    }

    // display the correct progress bar width based on percentage complete
    const percentage = Math.round((value / (maxValue - minValue)) * 100);
    progressBar.setAttribute('data-percent', percentage)
  };

  demoContLinear();
  const contLinearInterval = window.setInterval(demoContLinear, 6000);
</script>

Load Patterns

Types

There are two types of loading indicators: determinate and indeterminate. For clarity and consistency, use only one indicator per operation or page load.

A determinate loading indicator is used when the percentage completion of an operation is available and can be calculated. For example, a loading progress meter (either linear or circular) which cstarts at 0 and reaches 100% upon completion.

An indeterminate loading indicator is used when the customer doesn’t need to know how long an operation or load will take, or when percentage completion data isn’t available. For example, a looping linear bar or circular spinner that stays animated until the operation is complete.

If the progress bar is describing the loading progress of a particular region of a page, use aria-describedby to point to the status, and set the aria-live=“polite” to announce it is finished loading.


Linear Load Patterns


Example - Linear Determinate

<div role="progressbar" aria-valuemin="0" aria-valuemax="100"
     aria-valuenow="60" aria-label="Linear determinate"
     class="progress-bar" id="demo-linear">
  <div class="progress-track count-width"></div>
</div>
<div id="linear-live" aria-live="polite" class="flex flex-justify-end legal-1"></div>

<script>
  const linearAlert = document.getElementById('linear-live');

  function demoLinear() {
    let tracker = document.getElementById('demo-linear');
    if(!tracker) {
      window.clearInterval(linearInterval);
      return;
    }
    let value = Number(tracker.getAttribute('aria-valuenow'));
    const minValue = tracker.getAttribute('aria-valuemin');
    const maxValue = tracker.getAttribute('aria-valuemax');
    if(value >= maxValue) {
      value = 0;
      tracker.setAttribute('aria-valuenow', value);
      if(linearAlert) {
        linearAlert.innerHTML = tracker.getAttribute('aria-valuenow') + '%';
      }
    } else {
      value += 10;
      tracker.setAttribute('aria-valuenow', value);
      if(linearAlert) {
        linearAlert.innerHTML = tracker.getAttribute('aria-valuenow') + '%';
      }
    }
  };

  demoLinear();
  const linearInterval = window.setInterval(demoLinear, 1000);
</script>

Example - Linear Indeterminate

<div role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-label="Linear indeterminate"
     class="progress-bar progress-indeterminate">
  <div class="progress-track"></div>
</div>

Circular Load Patterns


Example - Circular Determinate

<div class="flex">
  <div role="progressbar" aria-valuemin="0" aria-valuemax="100"
       aria-valuenow="50" aria-label="Large circle determinate"
       class="progress-circle progress-determinate progress-lg margin-r" id="demo-cont-circ-lg">
    <svg role="presentation" aria-hidden="true">
      <circle cx="50%" cy="50%" r="45%" fill="none"></circle>
      <circle cx="50%" cy="50%" class="count-stroke" r="45%"></circle>
    </svg>
    <div class="progress-value percent"></div>
  </div>
  <span id="cont-circ-lg-live" aria-live="polite" class="sr-only"></span>

  <div role="progressbar" aria-valuemin="0" aria-valuemax="100"
       aria-valuenow="50" aria-label="Circle determinate"
       class="progress-circle progress-determinate progress" id="demo-cont-circ">
    <svg role="presentation" aria-hidden="true">
      <circle cx="50%" cy="50%" r="45%" fill="none"></circle>
      <circle cx="50%" cy="50%" class="count-stroke" r="45%"></circle>
    </svg>
    <div class="progress-value percent"></div>
  </div>
  <span id="cont-circ-live" aria-live="polite" class="sr-only"></span>
</div>

<script>
  const contCircLgAlert = document.getElementById('cont-circ-lg-live');
  const contCircAlert = document.getElementById('cont-circ-live');

  function demoContCirc () {
    let progressBarLg = document.getElementById('demo-cont-circ-lg');
    let progressBar = document.getElementById('demo-cont-circ');
    if(!progressBar) {
      window.clearInterval(contCircInterval);
      return;
    }

    let value = Number(progressBar.getAttribute('aria-valuenow'));
    const minValue = progressBar.getAttribute('aria-valuemin');
    const maxValue = progressBar.getAttribute('aria-valuemax');
    if(value >= maxValue) {
      value = 0;
      progressBarLg.setAttribute('aria-valuenow', value);
      progressBar.setAttribute('aria-valuenow', value);
      if(contCircLgAlert) {
        contCircLgAlert.innerHTML = progressBarLg.getAttribute('aria-valuenow') + '%';
      } if(contCircAlert) {
        contCircAlert.innerHTML = progressBar.getAttribute('aria-valuenow') + '%';
      }
    } else {
      value += 25;
      progressBarLg.setAttribute('aria-valuenow', value);
      progressBar.setAttribute('aria-valuenow', value);
      if(contCircLgAlert) {
        contCircLgAlert.innerHTML = progressBarLg.getAttribute('aria-valuenow') + '%';
      } if(contCircAlert) {
        contCircAlert.innerHTML = progressBar.getAttribute('aria-valuenow') + '%';
      }
    }
  };

  demoContCirc();
  const contCircInterval = window.setInterval(demoContCirc, 1000);
</script>

Example - Circular Indeterminate

<div role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-label="Large circle indeterminate" class="progress-circle progress-indeterminate progress-lg"></div>
<div role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-label="Circle indeterminate" class="progress-circle progress-indeterminate"></div>
<div role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-label="Small circle indeterminate" class="progress-circle progress-indeterminate progress-sm"></div>