progress indicator

The progress indicator comes in two variants and can be used to show a length of a process or express an unknown wait time, e.g. fetching a resource.

The first one is the determinate variant, which is a completable progress bar for which you can set the value via the style. The second variant is the indeterminate which is just a loading indicator for an unspecified wait time and therefore showing an animation only.

In order to make the component accessible each variant should provide an aria-label attribute or a label element which needs to be set by the user.

To make the content accessible when using the progress element to indicate the loading progress of a page section, the user should follow these steps:

  1. Set up an aria-label for the progress element, such as "Content loading..."
  2. Set up an aria-describedby attribute with the same value as the progress element's ID in the content that is being loaded.
  3. Set aria-busy="true" while the section is being loaded, and remove it once the content is fully loaded.

component variations

determinate progress indicator

<div class="a-progress-indicator-container">
  <progress
    class="a-progress-indicator -determinate"
    id="content-progress"
    value="37"
    max="100"
  ></progress>
  <div class="a-progress-indicator__inner-bar"></div>
</div>

indeterminate progress indicator

<div class="a-progress-indicator-container">
  <progress class="a-progress-indicator -indeterminate" max="100"></progress>
  <div class="a-progress-indicator__anim-bar">
    <div class="a-progress-indicator__inner-bar"></div>
  </div>
</div>

disabled progress indicator

<div class="a-progress-indicator-container">
  <progress
    class="a-progress-indicator -determinate -disabled"
    value="37"
    max="100"
  ></progress>
  <div class="a-progress-indicator__inner-bar"></div>
</div>

indeterminate disabled progress indicator

<div class="a-progress-indicator-container">
  <progress
    class="a-progress-indicator -indeterminate -disabled"
    max="100"
  ></progress>
  <div class="a-progress-indicator__anim-bar">
    <div class="a-progress-indicator__inner-bar"></div>
  </div>
</div>

progress example

<div class="frontend-kit-example_progress-indicator">
  <div class="a-progress-indicator-container">
    <progress
      class="a-progress-indicator -determinate"
      id="random-progress"
      max="100"
    ></progress>
    <div class="a-progress-indicator__inner-bar"></div>
  </div>
</div>

additional content

demo

// Add randomly between 2 to 10% progress.
function setProgress(container: Element): void {
  let currentValue = Number(container.getAttribute('data-progress'));
  if (currentValue === 100) {
    currentValue = 0;
  }

  const progress: number = Math.floor(Math.random() * (10 - 2) + 5);
  let newValue: number = currentValue + progress;

  if (newValue > 100) {
    newValue = 100;
  }
  container.setAttribute('data-progress', `${newValue}`);
}

export default (): void => {
  const examples = document.querySelectorAll('#random-progress');
  examples.forEach((container) => {
    window.setInterval(setProgress.bind(this, container), 1000);
  });
};

styles SCSS

$progress-indicator_indeterminate_width: 2rem;
$progress-indicator_height: 0.5rem;

@mixin dynamic-bar-animation-mixin {
  @keyframes dynamic-bar-animation {
    from {
      transform: translateX(0);
    }

    to {
      transform: translateX(
        calc(100% - $progress-indicator_indeterminate_width)
      );
    }
  }
}

.a-progress-indicator-container,
.a-progress-indicator__inner-bar,
progress {
  height: $progress-indicator_height;
}

.a-progress-indicator-container {
  min-width: 8rem;
  overflow: hidden; // Firefox's styling support
  position: relative;
}

progress {
  appearance: none; // Safari's styling support
  background-color: var(--minor-accent__enabled__fill__default);
  border: 0; // Firefox support
  box-shadow: inset 0 0 0 0.0625rem var(--minor-accent__enabled__front__default); // Firefox's styling support
  display: block;
  min-width: 8rem;
  width: 100%;

  // Chrome's styling support
  &::-webkit-progress-bar {
    background-color: var(--minor-accent__enabled__fill__default);
    box-shadow: inset 0 0 0 0.0625rem
      var(--minor-accent__enabled__front__default);
  }

  &::-webkit-progress-value {
    background-color: var(--minor-accent__enabled__front__default);
  }

  // Firefox's styling support
  &.-indeterminate::-moz-progress-bar {
    background-color: var(--minor-accent__enabled__fill__default);
    box-shadow: inset 0 0 0 0.0625rem
      var(--minor-accent__enabled__front__default);
  }

  &.-disabled {
    box-shadow: inset 0 0 0 0.0625rem
      var(--minor-accent__disabled__front__default); // Firefox

    // Chrome
    &::-webkit-progress-bar {
      background-color: var(--minor-accent__disabled__fill__default);
      box-shadow: inset 0 0 0 0.0625rem
        var(--minor-accent__disabled__front__default);
    }

    &::-webkit-progress-value {
      background-color: var(--minor-accent__disabled__front__default);
    }

    // Firefox
    &::-moz-progress-bar {
      background-color: var(--minor-accent__disabled__front__default);
      box-shadow: inset 0 0 0 0.0625rem
        var(--minor-accent__disabled__front__default);
    }

    &.-indeterminate::-moz-progress-bar {
      background-color: var(--minor-accent__disabled__fill__default);
    }

    + .a-progress-indicator__anim-bar .a-progress-indicator__inner-bar {
      background-color: var(--minor-accent__disabled__front__default);
    }
  }
}

.a-progress-indicator {
  position: absolute;
  transform: translateZ(0);

  &__anim-bar {
    @include dynamic-bar-animation-mixin;

    animation: dynamic-bar-animation 2s infinite linear;
  }

  &.-indeterminate
    + .a-progress-indicator__anim-bar
    .a-progress-indicator__inner-bar {
    width: $progress-indicator_indeterminate_width;
  }

  &.-indeterminate.-disabled + &__anim-bar {
    display: none;
  }

  &__inner-bar {
    background-color: var(--minor-accent__enabled__front__default);
    position: absolute;
  }
}