Progress
A semantic role="progressbar" bar that supports both bounded progress (value + max) and indeterminate mode (a wrapping sub-strip when the endpoint isn't known yet). shadcn-style: pure DOM, forwardRef, themeable via cb-* tokens.
Customize
Value
42
Max
100
Mode
Installation
npx shadcn@latest add https://craftbits.dev/r/progress.jsonUsage
import { Progress } from "@craft-bits/core";
<Progress value={42} />With a custom max:
<Progress value={3} max={5} />Indeterminate mode (omit value or pass indeterminate):
<Progress indeterminate />Understanding the component
- Transform-driven indicator. The fill is a span sized at 100% of the track, translated horizontally by
translateX(-${100 - percent}%). Onlytransformanimates, so the indicator rides on the compositor — no layout thrash, no width-animation pitfalls. - Indeterminate via CSS keyframes. When
valueisundefined(orindeterminateistrue), a sub-strip (~40% of the track) slides from-100%to250%on a 1.4s loop. The@keyframesrule is wrapped in@media (prefers-reduced-motion: no-preference)so reduced-motion users see a static tone-tinted track — no JS branching. - Style injection is idempotent. The indeterminate
@keyframesrule is appended to<head>once on first indeterminate mount, guarded by a sentinel<style id="cb-progress-indeterminate-keyframes">. Subsequent mounts skip injection. - Hydration-safe initial paint. The first render holds the indicator at 0% (matching SSR), then animates to the real percent post-mount — so
translateXvalues don't mismatch between server and client.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
value | number | null | undefined | Completion value in [0, max]. Leave undefined for indeterminate mode. |
max | number | 100 | Maximum value. Percentage = value / max * 100. |
indeterminate | boolean | value == null | Explicit override for indeterminate mode. |
className | string | — | Merged onto the rendered <div> via cn(). |
Accessibility
- Renders with
role="progressbar"andaria-valuemin={0}/aria-valuemax={max}. Determinate bars additionally exposearia-valuenowrounded to an integer. Indeterminate bars omitaria-valuenowper the ARIA spec. - The inner indicator is marked
aria-hidden="true"so assistive tech reads only the bar's accessible name — providearia-labeloraria-labelledbyfor context. - The indeterminate animation is automatically suppressed when
prefers-reduced-motion: reduceis set — the keyframes are scoped under@media (prefers-reduced-motion: no-preference). The determinate transition is short (300ms) and ends in a static state, so reduced-motion users still see the final position.
Credits
- Extracted from:
AlgoFlashcards(src/platform/ui/progress.tsx), originally a shadcn/Radix<Progress>wrapper. Re-architected as pure DOM (no Radix dep) withmaxandindeterminatesupport added.