Progress Bar

A thin horizontal bar that springs from its previous fill to a new target on every change. Five semantic tones, three sizes, and an indeterminate shimmer mode for when you don't know the endpoint yet.

Customize
Value
42
Tone
accent
Size
md
Mode

Installation

npx shadcn@latest add https://craftbits.dev/r/progress-bar.json

Usage

import { ProgressBar } from "@craft-bits/core";
 
<ProgressBar value={42} />

Leave value undefined for indeterminate mode:

<ProgressBar tone="accent" />

Understanding the component

  1. Motion-value driven fill. A useMotionValue holds the current percentage. On every value change, animate(mv, value, SPRINGS.smooth) springs the value toward the new target. The motion-value is mapped to a CSS width via useTransform, so React doesn't re-render every frame — the spring drives the DOM directly.
  2. Indeterminate via CSS keyframes. When value is undefined, a sub-strip (~40% of the track) slides from -100% to 250% on a 1.4s loop via transform: translateX. The @keyframes rule is wrapped in @media (prefers-reduced-motion: no-preference) so reduced-motion users see a static tone-tinted track — no JS branching.
  3. Style injection is idempotent. The shimmer @keyframes rule is appended to <head> once on first indeterminate mount, guarded by a sentinel <style id="cb-progress-shimmer-keyframes">. Subsequent mounts skip injection.
  4. Tone-driven palette. tone selects five semantic color combos (default, accent, success, warning, error). The track always uses bg-cb-bg-muted; only the fill picks up the tone — so the bar reads softly until there's progress to show.
  5. Reduced motion. When prefers-reduced-motion: reduce is set, the determinate fill snaps to its new target with no spring, and the indeterminate shimmer freezes via the CSS media query.

Props

PropTypeDefaultDescription
valuenumberundefinedCompletion percentage in [0, 100]. Leave undefined for indeterminate mode.
tone'default' | 'accent' | 'success' | 'warning' | 'error''accent'Semantic color for the fill.
size'sm' | 'md' | 'lg''md'Track height (1px / 1.5px / 2.5px scale).
transitionTransitionSPRINGS.smoothSpring transition for the determinate fill.
classNamestringMerged onto the rendered <div>.

Accessibility

  • Renders with role="progressbar" and aria-valuemin={0} / aria-valuemax={100}. Determinate bars additionally expose aria-valuenow rounded to an integer percent. Indeterminate bars omit aria-valuenow per ARIA spec.
  • The inner fill/shimmer is marked aria-hidden="true" so assistive tech reads only the bar's accessible name (provide aria-label or aria-labelledby for context).
  • The indeterminate shimmer animation is automatically disabled when prefers-reduced-motion: reduce is set — the keyframes are scoped under @media (prefers-reduced-motion: no-preference). The determinate spring snaps for the same users.

Credits

  • Extracted from: terminal-dreams (src/components/cookbook/ProgressBar.tsx).