Progress bars and stat tiles look like two categories and behave like one: both take a number and turn it into a shape, and both fail in the same way, which is claiming precision they do not have. A bar sitting at 90% while nothing moves is the interface equivalent of a countdown that stops at one.
72 public components on 21st have "progress" in the name and 127 say "stat". Below is what each shape is honest about, and where the two get confused.
Determinate progress
A number you actually know: bytes uploaded, steps completed, items processed. Progress Bar by ddoemonn is the accessible linear version with an animated fill and an indeterminate mode, and Progress Bar by Jatin Yadav is the segmented take, which reads better when the total is small and countable.
The rule: a determinate bar has to be determinate. If the value is a guess, use an indeterminate one and say what is happening in words. Padding a bar to look busy is the fastest way to teach a reader that your progress indicators mean nothing.
Steps
Progress Indicator by Anurag Mishra is the fluid step animation for onboarding and multi-stage flows, and Progressive Flux Loader by Ruixen UI is the labelled-phase version.
A step indicator is a different promise from a bar: it says how many stages there are, which is information a percentage hides. Use it whenever the total is a small known number, because "step 2 of 4" answers the reader's real question, which is how much longer.
Scroll progress
Scroll Progress by ibelick is the reading-position bar. It is the one progress indicator that is purely informational and never blocks anything, which is why it can be thin, quiet and permanent.
Stat tiles
Statistics Card by Erik X, Stats Widget by Ravi Katiyar and Featured Section Stats by Ruixen UI are the KPI shapes. Progress Metric Card by Mak VieSAinte is the version with a chart behind the figure, and Card Status List by Isaiah is the status-driven list.
Four rules make a row of tiles readable at a glance. One number per tile, at a size that is obviously the point. The comparison beside it, with the period named, because "+12%" without "vs last month" is not a fact. Tabular figures, so the digits line up across the row and the eye can compare columns rather than re-reading. And a consistent direction for good: green up is fine until the metric is churn, where up is bad, which is why the colour has to come from the interpretation rather than from the sign.




The markup, which is where the announcement lives
A progress bar built from two divs is invisible to a screen reader. The native element does most of the work:
If the design needs something the native element cannot style, use role="progressbar" with aria-valuenow, aria-valuemin, aria-valuemax and an accessible name, and drop aria-valuenow entirely for the indeterminate case rather than setting it to zero. Announce completion in a live region: a bar that quietly reaches the end tells a sighted reader everything and a screen reader user nothing.
For stat tiles, the number and its label belong in the same element, so they are read together. A large figure in one div and "Active users" in a sibling below reads as two unrelated fragments out of context.
Counting up
The animated counter deserves a word, because it is on almost every marketing page. It is fine, with two conditions: the final value must be in the DOM from the start so it exists for a crawler and for anyone who does not see the animation, and it must not run every time the element scrolls back into view. Under reduced motion, render the final number immediately.




Where else to look
The honest list of alternatives, because the answer is not always us:
| Source | Best for | Trade-off |
|---|---|---|
<progress> and <meter> | Native semantics and announcements for free | Limited styling, and <meter> is for a static measurement rather than a task |
| shadcn/ui Progress | A styled bar with the ARIA already correct | No steps, no stat tiles |
| shadcn/ui Charts | The sparkline inside a KPI card | Pulls in Recharts |
| 21st | Step indicators, KPI cards, status lists and scroll bars | Quality varies by author, so preview before you take it |
Taking one
Every component page has a live preview and the code. Installing goes through the shadcn CLI against our registry:
That key comes from your 21st account, and installs require a membership. Set API_KEY_21ST once in your shell and the command works for anything in the catalogue. The 21st MCP covers the same ground without leaving the editor: ask Claude, Cursor or Codex for a progress bar, get the previews inline, and let the agent write the file.
Frequently asked
- When should a progress bar be indeterminate?
- Whenever the value is a guess. A determinate bar promises the reader that the fill means something, and a bar padded to look busy teaches them that your indicators do not. If you cannot count the work, show an indeterminate state and name the step in words instead.
- How do you make a progress bar accessible?
- Use the native progress element with a label where the design allows it. Otherwise use role="progressbar" with aria-valuenow, aria-valuemin, aria-valuemax and an accessible name, and drop aria-valuenow entirely for the indeterminate case rather than setting it to zero. Announce completion in a live region.
- What makes a row of stat tiles readable?
- One number per tile at a size that is obviously the point, the comparison beside it with the period named, tabular figures so digits line up across the row, and a colour that comes from the interpretation rather than the sign, because up is bad for churn. Keep the number and its label in the same element so they are read together.
- Are animated counters a problem?
- Only when the final value exists solely in the animation. Put it in the DOM from the start so it is there for a crawler and for anyone who does not watch, do not re-run the count every time the element scrolls back into view, and render the final number immediately under prefers-reduced-motion.