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/progress
version3.1.3
usageimport {useProgressBar} from '@react-aria/progress'

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#


Progress bar anatomy diagramValueLabelLoading 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
progressBarPropsHTMLAttributes<HTMLElement>Props for the progress bar container element.
labelPropsHTMLAttributes<HTMLElement>Props 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#


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: 'gray'}}>
        <div style={{width: barWidth, height: 10, background: 'orange'}} />
      </div>
    </div>
  );
}

<ProgressBar label="Loading..." value={30} />
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: 'gray'}}>
        <div
          style={{
            width: barWidth,
            height: 10,
            background: 'orange'
          }}
        />
      </div>
    </div>
  );
}

<ProgressBar label="Loading..." value={30} />
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:
            'gray'
        }}>
        <div
          style={{
            width: barWidth,
            height: 10,
            background:
              'orange'
          }}
        />
      </div>
    </div>
  );
}

<ProgressBar
  label="Loading..."
  value={30}
/>

Indeterminate#


Progress bars can represent an indeterminate operation. They may also be used to represent progress visually as a circle rather than as a line. The following example shows an indeterminate progress bar visualized as a circular spinner using SVG.

function Spinner() {
  let {progressBarProps} = useProgressBar({
    isIndeterminate: true,
    'aria-label': 'Loading...'
  });

  let center = 16;
  let strokeWidth = 4;
  let r = 16 - strokeWidth;
  let c = 2 * r * Math.PI;
  let offset = c - (1 / 4) * 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}
        strokeDashoffset={offset}>
        <animateTransform
          attributeName="transform"
          type="rotate"
          begin="0s"
          dur="1s"
          from="0 16 16"
          to="360 16 16"
          repeatCount="indefinite"
        />
      </circle>
    </svg>
  );
}
function Spinner() {
  let {progressBarProps} = useProgressBar({
    isIndeterminate: true,
    'aria-label': 'Loading...'
  });

  let center = 16;
  let strokeWidth = 4;
  let r = 16 - strokeWidth;
  let c = 2 * r * Math.PI;
  let offset = c - (1 / 4) * 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}
        strokeDashoffset={offset}>
        <animateTransform
          attributeName="transform"
          type="rotate"
          begin="0s"
          dur="1s"
          from="0 16 16"
          to="360 16 16"
          repeatCount="indefinite"
        />
      </circle>
    </svg>
  );
}
function Spinner() {
  let {
    progressBarProps
  } = useProgressBar({
    isIndeterminate: true,
    'aria-label':
      'Loading...'
  });

  let center = 16;
  let strokeWidth = 4;
  let r =
    16 - strokeWidth;
  let c =
    2 * r * Math.PI;
  let offset =
    c - (1 / 4) * 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
        }
        strokeDashoffset={
          offset
        }>
        <animateTransform
          attributeName="transform"
          type="rotate"
          begin="0s"
          dur="1s"
          from="0 16 16"
          to="360 16 16"
          repeatCount="indefinite"
        />
      </circle>
    </svg>
  );
}

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.