useProgressBar

Provides the accessibility implementation for a progress bar component. Progress bars show either determinate or indeterminate progress of an operation over time.

installyarn add react-aria
version3.36.0
usageimport {useProgressBar} from 'react-aria'

API#


useProgressBar( (props: AriaProgressBarProps )): ProgressBarAria

Features#


The <progress> HTML element can be used to build a progress bar, however it is very difficult to style cross browser. useProgressBar helps achieve accessible progress bars and spinners that can be styled as needed.

  • Exposed to assistive technology as a progress bar via ARIA
  • Labeling support for accessibility
  • Internationalized number formatting as a percentage or value
  • Determinate and indeterminate progress support

Anatomy#


Shows a progress bar with labels pointing to its parts, including the label, fill, track, and value label elements.ValueLabelLoading data…26%TrackFill

Progress bars consist of a track element showing the full progress of an operation, a fill element showing the current progress, a label, and an optional value label. The track and bar elements represent the progress visually, while a wrapper element represents the progress to assistive technology using the progressbar ARIA role.

useProgressBar returns two sets of props that you should spread onto the appropriate element:

NameTypeDescription
progressBarPropsDOMAttributesProps for the progress bar container element.
labelPropsDOMAttributesProps for the progress bar's visual label element (if any).

If there is no visual label, an aria-label or aria-labelledby prop must be passed instead to identify the element to screen readers.

Example#


import {useProgressBar} from 'react-aria';

function ProgressBar(props) {
  let {
    label,
    showValueLabel = !!label,
    value,
    minValue = 0,
    maxValue = 100
  } = props;
  let {
    progressBarProps,
    labelProps
  } = useProgressBar(props);

  // Calculate the width of the progress bar as a percentage
  let percentage = (value - minValue) / (maxValue - minValue);
  let barWidth = `${Math.round(percentage * 100)}%`;

  return (
    <div {...progressBarProps} style={{ width: 200 }}>
      <div style={{ display: 'flex', justifyContent: 'space-between' }}>
        {label &&
          (
            <span {...labelProps}>
              {label}
            </span>
          )}
        {showValueLabel &&
          (
            <span>
              {progressBarProps['aria-valuetext']}
            </span>
          )}
      </div>
      <div style={{ height: 10, background: 'lightgray' }}>
        <div style={{ width: barWidth, height: 10, background: 'orange' }} />
      </div>
    </div>
  );
}

<ProgressBar label="Loading…" value={80} />
import {useProgressBar} from 'react-aria';

function ProgressBar(props) {
  let {
    label,
    showValueLabel = !!label,
    value,
    minValue = 0,
    maxValue = 100
  } = props;
  let {
    progressBarProps,
    labelProps
  } = useProgressBar(props);

  // Calculate the width of the progress bar as a percentage
  let percentage = (value - minValue) /
    (maxValue - minValue);
  let barWidth = `${Math.round(percentage * 100)}%`;

  return (
    <div {...progressBarProps} style={{ width: 200 }}>
      <div
        style={{
          display: 'flex',
          justifyContent: 'space-between'
        }}
      >
        {label &&
          (
            <span {...labelProps}>
              {label}
            </span>
          )}
        {showValueLabel &&
          (
            <span>
              {progressBarProps['aria-valuetext']}
            </span>
          )}
      </div>
      <div style={{ height: 10, background: 'lightgray' }}>
        <div
          style={{
            width: barWidth,
            height: 10,
            background: 'orange'
          }}
        />
      </div>
    </div>
  );
}

<ProgressBar label="Loading…" value={80} />
import {useProgressBar} from 'react-aria';

function ProgressBar(
  props
) {
  let {
    label,
    showValueLabel =
      !!label,
    value,
    minValue = 0,
    maxValue = 100
  } = props;
  let {
    progressBarProps,
    labelProps
  } = useProgressBar(
    props
  );

  // Calculate the width of the progress bar as a percentage
  let percentage =
    (value - minValue) /
    (maxValue -
      minValue);
  let barWidth = `${
    Math.round(
      percentage * 100
    )
  }%`;

  return (
    <div
      {...progressBarProps}
      style={{
        width: 200
      }}
    >
      <div
        style={{
          display:
            'flex',
          justifyContent:
            'space-between'
        }}
      >
        {label &&
          (
            <span
              {...labelProps}
            >
              {label}
            </span>
          )}
        {showValueLabel &&
          (
            <span>
              {progressBarProps[
                'aria-valuetext'
              ]}
            </span>
          )}
      </div>
      <div
        style={{
          height: 10,
          background:
            'lightgray'
        }}
      >
        <div
          style={{
            width:
              barWidth,
            height: 10,
            background:
              'orange'
          }}
        />
      </div>
    </div>
  );
}

<ProgressBar
  label="Loading…"
  value={80}
/>

Circular example#


Progress bars may also be represented using a circular visualization rather than a line. This is often used to represent indeterminate operations, but may also be used for determinate progress indicators when space is limited. The following example shows a progress bar visualized as a circular spinner using SVG.

function ProgressCircle(props) {
  let { isIndeterminate, value, minValue = 0, maxValue = 100 } = props;
  let { progressBarProps } = useProgressBar(props);

  let center = 16;
  let strokeWidth = 4;
  let r = 16 - strokeWidth;
  let c = 2 * r * Math.PI;
  let percentage = isIndeterminate
    ? 0.25
    : (value - minValue) / (maxValue - minValue);
  let offset = c - percentage * c;

  return (
    <svg
      {...progressBarProps}
      width={32}
      height={32}
      viewBox="0 0 32 32"
      fill="none"
      strokeWidth={strokeWidth}
    >
      <circle
        role="presentation"
        cx={center}
        cy={center}
        r={r}
        stroke="gray"
      />
      <circle
        role="presentation"
        cx={center}
        cy={center}
        r={r}
        stroke="orange"
        strokeDasharray={`${c} ${c}`}
        strokeDashoffset={offset}
        transform="rotate(-90 16 16)"
      >
        {props.isIndeterminate &&
          (
            <animateTransform
              attributeName="transform"
              type="rotate"
              begin="0s"
              dur="1s"
              from="0 16 16"
              to="360 16 16"
              repeatCount="indefinite"
            />
          )}
      </circle>
    </svg>
  );
}

<ProgressCircle aria-label="Loading…" value={60} />
function ProgressCircle(props) {
  let {
    isIndeterminate,
    value,
    minValue = 0,
    maxValue = 100
  } = props;
  let { progressBarProps } = useProgressBar(props);

  let center = 16;
  let strokeWidth = 4;
  let r = 16 - strokeWidth;
  let c = 2 * r * Math.PI;
  let percentage = isIndeterminate
    ? 0.25
    : (value - minValue) / (maxValue - minValue);
  let offset = c - percentage * c;

  return (
    <svg
      {...progressBarProps}
      width={32}
      height={32}
      viewBox="0 0 32 32"
      fill="none"
      strokeWidth={strokeWidth}
    >
      <circle
        role="presentation"
        cx={center}
        cy={center}
        r={r}
        stroke="gray"
      />
      <circle
        role="presentation"
        cx={center}
        cy={center}
        r={r}
        stroke="orange"
        strokeDasharray={`${c} ${c}`}
        strokeDashoffset={offset}
        transform="rotate(-90 16 16)"
      >
        {props.isIndeterminate &&
          (
            <animateTransform
              attributeName="transform"
              type="rotate"
              begin="0s"
              dur="1s"
              from="0 16 16"
              to="360 16 16"
              repeatCount="indefinite"
            />
          )}
      </circle>
    </svg>
  );
}

<ProgressCircle aria-label="Loading…" value={60} />
function ProgressCircle(
  props
) {
  let {
    isIndeterminate,
    value,
    minValue = 0,
    maxValue = 100
  } = props;
  let {
    progressBarProps
  } = useProgressBar(
    props
  );

  let center = 16;
  let strokeWidth = 4;
  let r = 16 -
    strokeWidth;
  let c = 2 * r *
    Math.PI;
  let percentage =
    isIndeterminate
      ? 0.25
      : (value -
        minValue) /
        (maxValue -
          minValue);
  let offset = c -
    percentage * c;

  return (
    <svg
      {...progressBarProps}
      width={32}
      height={32}
      viewBox="0 0 32 32"
      fill="none"
      strokeWidth={strokeWidth}
    >
      <circle
        role="presentation"
        cx={center}
        cy={center}
        r={r}
        stroke="gray"
      />
      <circle
        role="presentation"
        cx={center}
        cy={center}
        r={r}
        stroke="orange"
        strokeDasharray={`${c} ${c}`}
        strokeDashoffset={offset}
        transform="rotate(-90 16 16)"
      >
        {props
          .isIndeterminate &&
          (
            <animateTransform
              attributeName="transform"
              type="rotate"
              begin="0s"
              dur="1s"
              from="0 16 16"
              to="360 16 16"
              repeatCount="indefinite"
            />
          )}
      </circle>
    </svg>
  );
}

<ProgressCircle
  aria-label="Loading…"
  value={60}
/>

Usage#


The following examples show how to use the ProgressBar and ProgressCircle components created in the above example.

Custom value scale#

By default, the value prop represents the current percentage of progress, as the minimum and maximum values default to 0 and 100, respectively. Alternatively, a different scale can be used by setting the minValue and maxValue props.

<ProgressBar
  label="Loading…"
  minValue={50}
  maxValue={150}
  value={100} />
<ProgressBar
  label="Loading…"
  minValue={50}
  maxValue={150}
  value={100} />
<ProgressBar
  label="Loading…"
  minValue={50}
  maxValue={150}
  value={100} />

Value formatting#

Values are formatted as a percentage by default, but this can be modified by using the formatOptions prop to specify a different format. formatOptions is compatible with the option parameter of Intl.NumberFormat and is applied based on the current locale.

<ProgressBar
  label="Sending…"
  formatOptions={{style: 'currency', currency: 'JPY'}}
  value={60} />
<ProgressBar
  label="Sending…"
  formatOptions={{style: 'currency', currency: 'JPY'}}
  value={60} />
<ProgressBar
  label="Sending…"
  formatOptions={{
    style: 'currency',
    currency: 'JPY'
  }}
  value={60}
/>

Custom value label#

The valueLabel prop allows the formatted value to be replaced with a custom string.

<ProgressBar
  label="Feeding…"
  valueLabel="30 of 100 dogs"
  value={30} />
<ProgressBar
  label="Feeding…"
  valueLabel="30 of 100 dogs"
  value={30} />
<ProgressBar
  label="Feeding…"
  valueLabel="30 of 100 dogs"
  value={30}
/>

Indeterminate#

The isIndeterminate prop can be used to represent an indeterminate operation. The ProgressCircle component created above implements an animation to indicate this visually.

<ProgressCircle
  aria-label="Loading…"
  isIndeterminate />
<ProgressCircle
  aria-label="Loading…"
  isIndeterminate />
<ProgressCircle
  aria-label="Loading…"
  isIndeterminate />

Internationalization#


Value formatting#

useProgressBar will handle localized formatting of the value label for accessibility automatically. This is returned in the aria-valuetext prop in progressBarProps. You can use this to create a visible label if needed and ensure that it is formatted correctly. The number formatting can also be controlled using the formatOptions prop.

RTL#

In right-to-left languages, the progress bar should be mirrored. The label is right-aligned, the value is left-aligned, and the fill progresses from right to left. Ensure that your CSS accounts for this.