Skip to content
Primitives

Progress

Bordered track with a signal indicator, plus an indeterminate `busy` sweep and an optional mono label.

● LIVE
npx shadcn@latest add https://okibi.cndr.dev/r/progress.json

Installation

Install the theme first, then add the component:

npx shadcn@latest add https://okibi.cndr.dev/r/progress.json

Install the required dependencies:

npm install @radix-ui/react-progress

Copy the source from the Code tab above into components/ui/progress.tsx. It uses the cn helper and the okibi theme tokens — install the theme first if you haven't.

Usage

import { Progress } from "@/components/ui/progress"

<Progress value={66} />

Examples

Busy

Set busy for an indeterminate "channel busy" state — twin signal-rail dash layers sweep in opposite directions and the bar is reported indeterminate (no aria-valuenow). Add a label for a mono readout row above the track.

● LIVE
Uplink // TX
Buffer

API Reference

Progress

PropTypeDefaultDescription
valuenumberFill percentage (0–100).
busybooleanfalseIndeterminate 'channel busy' sweep instead of a fill.
busySpeednumber0.5Relative speed of the indeterminate busy sweep.
labelstringOptional mono label above the track.

Accessibility

  • Built on the Radix progressbar — a determinate value reports aria-valuenow, so assistive tech announces the percentage.
  • Pass a label and it is auto-wired as the bar's accessible name via a generated id and aria-labelledby, so the gauge announces what is progressing.
  • Explicit aria-label or aria-labelledby are forwarded to the track and win over the label auto-naming — supply one when the bar has no visible label.
  • In busy mode the value is omitted so Radix reports an indeterminate state (no aria-valuenow), and the sweep layers are aria-hidden.
  • The mono percentage / ··· TX readout is decorative and aria-hidden; the announced value comes from the bar itself, never that text.
  • The busy sweep is gated by the global reduced-motion guard, resting at a static row of signal dashes.

Guidelines

Do

  • Give every bar an accessible name — pass label, or an aria-label / aria-labelledby when the label lives elsewhere.
  • Use busy for indeterminate, no-known-duration work; reserve a numeric value for measurable progress.
  • Keep value in the 0–100 range — it defaults to 0 so the track renders before data lands.

Don't

  • Don't ship an unlabeled Progress — a bare bar has no accessible name and announces nothing useful.
  • Don't set both value and busy; busy deliberately drops the value to read as indeterminate.
  • Don't lean on the visible percentage readout for meaning — it's aria-hidden decoration.

On this page