# Next.js Bootstrap Progress Bar

A Next.js Bootstrap progress bar in AdminKit PRO: server-rendered quotas, a live value from client state, aria-valuenow, labels, colors and stacked bars.

> This component is only available in **AdminKit PRO**. [Learn more](/pricing/#nextjs).

A progress bar tells people how far along something is: how much of a storage quota is used, or how much of an upload is done. It is Bootstrap's `.progress` markup in JSX, with no component to import. Progress bars are part of the PRO version of [AdminKit for Next.js](/nextjs/).

Where the value comes from decides where the bar lives. In the App Router, pages are server components: they render on the server and have no state. A bar whose value is known when the page renders, such as a quota on the settings page, is plain markup in a server component, with no `"use client"`. A bar that moves while the user watches, such as an upload, needs state. It goes in a client component, a file that starts with `"use client"`. Either way, one value sets both the width and the `aria-valuenow`.

This is a progress bar inside the page content. It is not a loading bar at the top of the window that runs on route changes: AdminKit for Next.js does not include one.

## Example

```html
<div class="progress" role="progressbar" aria-label="Upload" aria-valuenow="65" aria-valuemin="0" aria-valuemax="100" style="max-width: 24rem;">
  <div class="progress-bar" style="width: 65%"></div>
</div>
```
```tsx code
export function UploadProgress({ value }: { value: number }) {
  return (
    <div
      className="progress"
      role="progressbar"
      aria-label="Upload"
      aria-valuenow={value}
      aria-valuemin={0}
      aria-valuemax={100}
    >
      <div className="progress-bar" style={{ width: `${value}%` }} />
    </div>
  );
}
```

`UploadProgress` uses no hooks, so it works in both places. A server component can render it with a fixed value, and a client component can pass it a value from state. The `aria-value*` props take numbers, and the width is a template string in a `style` object. When the value changes, Bootstrap animates the width.

## Classes

| Class | Effect |
| --- | --- |
| `progress` | The 1rem high track, with `role="progressbar"` and the `aria-value*` attributes |
| `progress-bar` | The filled part, as wide as the value |
| `progress-sm`, `progress-lg` | AdminKit: a 0.4rem or 1.5rem high track |
| `bg-<color>` | The bar color: `bg-success`, `bg-warning`, ... |
| `progress-bar-striped` | Stripes over the color |
| `progress-bar-animated` | Moving stripes |
| `progress-stacked` | Several `.progress` elements as segments of one bar |

## Quota in a server component

A quota is usually known when the page renders, so its bar can stay in a server component. The billing card of the settings page renders its bars on the server. Each bar has a visible label that `aria-labelledby` points to. A value that is not a percentage, such as 18.4 GB of 100 GB, is converted first:

```html
<div style="max-width: 24rem;">
  <div class="d-flex justify-content-between mb-1">
    <span id="storage-label">Storage</span>
    <span class="text-body-secondary">18.4 GB of 100 GB</span>
  </div>
  <div class="progress progress-sm" role="progressbar" aria-labelledby="storage-label" aria-valuenow="18" aria-valuemin="0" aria-valuemax="100">
    <div class="progress-bar" style="width: 18%"></div>
  </div>
</div>
```
```tsx code title="src/components/storage-usage.tsx"
export function StorageUsage({ used, total }: { used: number; total: number }) {
  const percent = Math.min(Math.round((used / total) * 100), 100);

  return (
    <>
      <div className="d-flex justify-content-between mb-1">
        <span id="storage-label">Storage</span>
        <span className="text-body-secondary">
          {used} GB of {total} GB
        </span>
      </div>
      <div
        className="progress progress-sm"
        role="progressbar"
        aria-labelledby="storage-label"
        aria-valuenow={percent}
        aria-valuemin={0}
        aria-valuemax={100}
      >
        <div className="progress-bar" style={{ width: `${percent}%` }} />
      </div>
    </>
  );
}
```

`Math.min(..., 100)` clamps the value, as on the CRM dashboard, so a quota over 100% stays inside the track.

In a static export, the server render happens once, at build time. A value that changes after the build has to be fetched in the browser, in a client component.

## Labels, colors and stripes

To show the value on the bar itself, put text inside `.progress-bar`. Keep it short, and only use it on bars that are wide enough. Colors and stripes are classes on the same element:

```html
<div class="progress mb-3" role="progressbar" aria-label="Tasks done" aria-valuenow="25" aria-valuemin="0" aria-valuemax="100">
  <div class="progress-bar bg-success" style="width: 25%">25%</div>
</div>
<div class="progress mb-3" role="progressbar" aria-label="Import" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100">
  <div class="progress-bar progress-bar-striped" style="width: 50%"></div>
</div>
<div class="progress" role="progressbar" aria-label="Sync" aria-valuenow="75" aria-valuemin="0" aria-valuemax="100">
  <div class="progress-bar progress-bar-striped progress-bar-animated bg-info" style="width: 75%"></div>
</div>
```
```tsx code
<div className="progress" role="progressbar" aria-label="Tasks done" aria-valuenow={25} aria-valuemin={0} aria-valuemax={100}>
  <div className="progress-bar bg-success" style={{ width: "25%" }}>25%</div>
</div>
<div className="progress" role="progressbar" aria-label="Import" aria-valuenow={50} aria-valuemin={0} aria-valuemax={100}>
  <div className="progress-bar progress-bar-striped" style={{ width: "50%" }} />
</div>
<div className="progress" role="progressbar" aria-label="Sync" aria-valuenow={75} aria-valuemin={0} aria-valuemax={100}>
  <div className="progress-bar progress-bar-striped progress-bar-animated bg-info" style={{ width: "75%" }} />
</div>
```

## Stacked bars

To split one bar into parts, such as paid, pending and overdue, use `.progress-stacked`. It holds one `.progress` per segment, each with its own value and label:

```html
<div class="progress-stacked">
  <div class="progress" role="progressbar" aria-label="Paid" aria-valuenow="45" aria-valuemin="0" aria-valuemax="100" style="width: 45%">
    <div class="progress-bar bg-success"></div>
  </div>
  <div class="progress" role="progressbar" aria-label="Pending" aria-valuenow="30" aria-valuemin="0" aria-valuemax="100" style="width: 30%">
    <div class="progress-bar bg-warning"></div>
  </div>
  <div class="progress" role="progressbar" aria-label="Overdue" aria-valuenow="10" aria-valuemin="0" aria-valuemax="100" style="width: 10%">
    <div class="progress-bar bg-danger"></div>
  </div>
</div>
```
```tsx code
const segments = [
  { label: "Paid", value: 45, color: "success" },
  { label: "Pending", value: 30, color: "warning" },
  { label: "Overdue", value: 10, color: "danger" },
];

<div className="progress-stacked">
  {segments.map((segment) => (
    <div
      key={segment.label}
      className="progress"
      role="progressbar"
      aria-label={segment.label}
      aria-valuenow={segment.value}
      aria-valuemin={0}
      aria-valuemax={100}
      style={{ width: `${segment.value}%` }}
    >
      <div className={`progress-bar bg-${segment.color}`} />
    </div>
  ))}
</div>
```

## Accessibility

- Name every bar with `aria-label`, or with `aria-labelledby` pointing at its visible label.
- Compute `aria-valuenow` and the width from the same variable, so both always agree.
- For a value that is not a percentage, such as 18.4 GB, add `aria-valuetext` with that text.
- Text on a `.progress-bar.bg-warning` is dark in AdminKit PRO, because white text does not reach a 4.5:1 contrast on it.

## Related

- [React Bootstrap progress bar](/react/docs/components/progress/): the same markup in AdminKit for React (Vite)
- [Bootstrap 5 progress](/bootstrap/docs/components/progress/): the progress bars of the HTML version
- [Spinners](/nextjs/docs/components/spinners/): a loading state without a known value
- [General UI demo](https://nextjs-demo.adminkit.io/ui-general)

---

Canonical HTML page: https://adminkit.io/nextjs/docs/components/progress/

Site overview for agents: https://adminkit.io/llms.txt (expanded: https://adminkit.io/llms-full.txt) · Documentation: https://adminkit.io/bootstrap/docs/ · Questions: support@adminkit.io
