DriversRecommendedOutdated drivers can make a good PC feel brokenScan driver issues before chasing fixes manually.Scan NowOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsSlow PC?RecommendedPC slow today? Run a repair scan before it gets worseResolve common Windows issues and optimize system performance.Scan Now×
Skip to content
World desk4 min

Build an Accessible Custom React Progress Bar Component

Learn when to style native and how to implement accessible custom progressbar markup in React, including unknown progress and value ranges.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For most React interfaces, start with the native <progress> element and style it. It already communicates progress to assistive technology and supports both known and unknown completion amounts. Build a custom element with role="progressbar" only when native markup cannot meet the visual or DOM requirement—and then implement its accessibility details yourself.

Start with native <progress> when it fits

The native element is the simplest reusable option. In React, its value is a number from zero through max; max defaults to 1, so set it explicitly when your component uses percentages. React treats value={null} as indeterminate progress. [React]

function ProgressBar({ value, label = "Progress" }) {
  const indeterminate = value == null;

  return (
    <label className="progress">
      <span className="progress__label">{label}</span>
      <progress
        className="progress__track"
        value={indeterminate ? undefined : value}
        max={100}
        aria-label={label}
      />
      {!indeterminate && <span>{value}%</span>}
    </label>
  );
}

This is a starting point, not a complete input policy. Decide what the component should do with values below zero or above 100, and whether the displayed percentage should be rounded. Keep the visible wording and accessible name clear in the consuming layout; in this example, both are provided, so avoid adding another competing label around the component.

The native element has a minimum of zero and an implicit progressbar role. Its max must be greater than zero. Text placed between the opening and closing <progress> tags is fallback content, not the accessible label; provide a name separately. Browser styling varies, so style the element where supported and check the result in the browsers your interface targets. [MDN: <progress>]

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Choose the implementation that matches the requirement

Option Best fit What you take on
Styled native <progress> The desired design can be achieved with the native element. Provide an accessible label; browser styling differs. Range and indeterminate behavior are built in. [MDN]
Custom element with role="progressbar" Your DOM or rendering needs cannot be met adequately with <progress>. Implement the accessible name, range, current value or indeterminate state, and visual updates. The ARIA role alone does not give a generic element native behavior. [MDN: progressbar role]
React Aria ProgressBar You want a documented library component with richer behavior. Review its dependency cost and API fit; its documentation describes determinate and indeterminate progress and locale-aware value formatting. [React Aria]

Use the native semantic element when it meets the requirement. A generic element gives more direct control over the markup, but it also makes range semantics and state management your responsibility. A progress bar represents completion of a task; it is not a gauge for a measurement such as disk space or a query’s relevance. [MDN: <progress>]

Build custom ARIA markup only when necessary

For custom markup, put role="progressbar" on the semantic wrapper. Give it an accessible name using aria-labelledby to reference a visible label outside the wrapper, or use aria-label. Descendants of a progressbar are treated as presentational, so do not put essential label text inside it. [MDN: progressbar role]

function CustomProgressBar({ value, label }) {
  const indeterminate = value == null;

  return (
    <div>
      <span id="upload-label">{label}</span>
      <div
        className="progress"
        role="progressbar"
        aria-labelledby="upload-label"
        aria-valuemin={0}
        aria-valuemax={100}
        aria-valuenow={indeterminate ? undefined : value}
      >
        <div className="track">
          <div
            className="fill"
            style={{ width: indeterminate ? "35%" : `${value}%` }}
          />
        </div>
      </div>
    </div>
  );
}

The example assumes that determinate values have already been validated to fall from zero through 100; add that validation or clamping before rendering. If your range is not zero through 100, expose the actual minimum and maximum and keep the current value within them. When users need a spoken value other than the numeric one—for example, a meaningful unit or stage—provide aria-valuetext as appropriate. [MDN: progressbar role]

Represent unknown progress honestly

When completion cannot be expressed as a known number, omit aria-valuenow and do not show a fabricated percentage. The animated fill in a custom indeterminate bar is only a visual activity cue; its apparent width must not suggest an exact completion amount. With native markup, omit the value attribute; in React, use value={null}. [React] [MDN: <progress>] [MDN: progressbar role]

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Connect progress to the region it describes

If the bar describes a page region being updated, connect the indicator and region: set aria-describedby on the region to reference the progress indicator, and set aria-busy="true" on the region while the update is underway. Clear the busy state when the update finishes. [MDN: <progress>]

Check these details before using the component

  • Give the indicator a concise accessible name, such as “Uploading report.”
  • Use a determinate value only when the completed amount is known; otherwise expose an indeterminate state.
  • Keep determinate values inside the component’s chosen minimum and maximum.
  • For custom ARIA markup, keep essential label text outside the progressbar and reference it with aria-labelledby.
  • Use a progress bar for task completion, not for a gauge or general measurement.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
Crashes, No Sound, or Screen Glitches?Free driver scan
Windows Errors? Fix Them Before They SpreadFree repair scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.