How to Create a Progress Bar in CSS

Build a progress bar that visually communicates how far along a task or value is, using either the native <progress> element or a styled div.

What a progress bar communicates

A progress bar turns a number into a visual proportion: a file upload at 40%, a multi-step form on step 3 of 5, a skill level out of 100. There are two ways to build one on the web: the native `<progress>` element, which is semantic and accessible for free, and a div-based bar, which gives you full control over colors, stripes, and animation.

Option 1: the native <progress> element

`<progress>` takes a `value` and a `max` attribute and the browser renders the fill automatically. Because it's a real form-associated element, screen readers announce it as a progress indicator with its current percentage - something a plain div can never do without extra ARIA attributes.

Example: styled native <progress>

<!DOCTYPE html>
<html>
<head>
<style>
  progress {
    width: 100%;
    height: 18px;
    border-radius: 9px;
    overflow: hidden; /* needed so the rounded corners clip the fill in Firefox */
    border: none;
  }

  /* Chrome/Edge/Safari track and fill */
  progress::-webkit-progress-bar {
    background: #e6e8eb;
  }
  progress::-webkit-progress-value {
    background: #2f7d5b;
    transition: width 0.2s ease;
  }

  /* Firefox track and fill */
  progress::-moz-progress-bar {
    background: #2f7d5b;
  }
</style>
</head>
<body>

<progress value="40" max="100"></progress>

</body>
</html>
Note: The track and the fill of <progress> must be styled with separate vendor-prefixed pseudo-elements (::-webkit-progress-bar/::-webkit-progress-value in Chromium, ::-moz-progress-bar in Firefox) because there is no standardized way yet to style it with plain properties like background.

Option 2: a div-based bar for full control

When you need gradients, striped animation, or a percentage label drawn inside the bar, build it from two nested divs: an outer `.progress-track` that acts as the background, and an inner `.progress-fill` whose `width` is set to the percentage - either directly in CSS or dynamically with JavaScript.

Example: div-based progress bar updated with JavaScript

<!DOCTYPE html>
<html>
<head>
<style>
  .progress-track {
    width: 100%;
    height: 18px;
    background: #e6e8eb;
    border-radius: 9px;
    overflow: hidden;
  }
  .progress-fill {
    height: 100%;
    width: 0%; /* JavaScript sets the real value */
    background: linear-gradient(90deg, #3b82f6, #2563eb);
    border-radius: 9px;
    transition: width 0.3s ease;
    text-align: right;
    color: #fff;
    font-size: 0.7rem;
    line-height: 18px;
    padding-right: 6px;
    box-sizing: border-box;
    white-space: nowrap;
  }
</style>
</head>
<body>

<div class="progress-track">
  <div class="progress-fill" id="fill">0%</div>
</div>
<button id="load-btn">Simulate loading</button>

<script>
  var fill = document.getElementById('fill');
  var button = document.getElementById('load-btn');

  button.addEventListener('click', function () {
    var percent = 0;
    var timer = setInterval(function () {
      percent += 10;
      fill.style.width = percent + '%';
      fill.textContent = percent + '%';
      if (percent >= 100) clearInterval(timer);
    }, 200);
  });
</script>

</body>
</html>

Setting `fill.style.width` to a percentage string is what makes the bar grow - the CSS `transition: width` line then animates that change smoothly instead of jumping instantly to the new width.

  • Use <progress> when the value is a genuine, known measurement (like file upload percentage).
  • Use a div-based bar when you need custom colors, stripes, or an inline label.
  • Whichever you choose, keep the numeric value visible somewhere in text too, for users who cannot rely on color alone.
  • If you build a div-based bar, add role="progressbar" plus aria-valuenow, aria-valuemin, and aria-valuemax attributes on the track so assistive technology can announce it the same way it would announce a native <progress> element.
Feature<progress> elementDiv-based bar
Accessible by defaultYesOnly with manual ARIA attributes
Custom gradients/stripesLimitedFull control
Cross-browser styling effortNeeds vendor prefixesPlain CSS

Frequently Asked Questions

How do I create a progress bar in CSS?
Nest a fill div inside a track div. Give the track a background and a fixed height, then set the fill's width as a percentage. Add a transition on width so changes animate rather than jumping.
Should I use the HTML progress element instead?
Use <progress> when you are reporting real task completion, since it carries the right semantics for screen readers. Build your own from divs when you need full styling control, as <progress> is awkward to style consistently across browsers.