Light

Progress

Displays an indicator showing the completion progress of a task, typically displayed as a progress bar.

Spec · from metadata

When to use

  • Showing upload, download, or processing progress
  • Indicating completion percentage of a multi-step flow
  • Displaying a metric as a filled bar (e.g., storage usage)

When not to use

  • For indeterminate loading states — use Skeleton or a spinner instead
  • For step-by-step progress — use a Stepper component instead
  • For circular progress — not supported; use a custom radial component or Chart

Anti-patterns

Avoid<Progress value={0.66} />
Prefer<Progress value={66} />

Value is a percentage (0-100), not a decimal fraction

Avoid<Progress className="bg-blue-500" value={50} />
Prefer<Progress value={50} />

className applies to the track (Root), not the indicator; use the design system's bg-primary for the fill, which is set by default

Accessibility

  • Required ARIAaria-valuenow (set from value prop by Radix)aria-valuemin (0, set by Radix)aria-valuemax (100, set by Radix)
  • Screen readerRadix sets role='progressbar' and aria-valuenow automatically; add aria-label if context is not clear from surrounding text

Token bindings

TokenCategoryUsage
bg-primary/20colorTrack (unfilled) background
bg-primarycolorIndicator (filled) bar color
rounded-fullradiusFully rounded track ends

Import

import { Progress } from "@timelycare/helix-ui"

Props

interface ProgressProps {
  value?: number          // 0-100 percentage
  max?: number            // Default 100
  className?: string
}

Structure

PartTailwind
Root (track)relative h-2 w-full overflow-hidden rounded-full bg-primary/20
Indicatorh-full bg-primary rounded-full transition-all

Anatomy

┌─────────────────────────────────────────┐  ← Root (bg-primary/20)
│████████████████████                     │  ← Indicator (bg-primary)
└─────────────────────────────────────────┘
         └── width based on value prop

Common Patterns

Basic Progress

<Progress value={33} />

With Label

<div className="space-y-2">
  <div className="flex justify-between text-sm">
    <span>Progress</span>
    <span className="text-muted-foreground">33%</span>
  </div>
  <Progress value={33} />
</div>

Animated Progress

const [progress, setProgress] = useState(0)

useEffect(() => {
  const timer = setTimeout(() => setProgress(66), 500)
  return () => clearTimeout(timer)
}, [])

<Progress value={progress} />

Task Completion

const completed = 3
const total = 5

<div className="space-y-2">
  <p className="text-sm text-muted-foreground">
    {completed} of {total} tasks completed
  </p>
  <Progress value={(completed / total) * 100} />
</div>

States

StateAppearance
Empty (0%)Only track visible (bg-primary/20)
PartialIndicator fills proportionally
Complete (100%)Indicator fills entire track
IndeterminateUse CSS animation (not built-in)

Indeterminate Pattern

// Add to globals.css
@keyframes progress-indeterminate {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(400%); }
}

// Component usage
<Progress 
  value={undefined} 
  className="[&>div]:animate-[progress-indeterminate_1.5s_ease-in-out_infinite] [&>div]:w-1/4" 
/>

Accessibility

  • Built on Radix Progress — handles ARIA attributes automatically
  • Applies role="progressbar" with aria-valuenow and aria-valuemax
  • Screen reader: announces progress value
  • No keyboard interaction needed (informational element)
  • Add aria-label to describe what is loading

Gotchas

ProblemSolution
Value exceeds 100Clamp value: Math.min(100, value)
No visual feedback at 0%Track (bg-primary/20) always visible
Jerky animationAdd transition-all to indicator
Indeterminate not supportedUse custom CSS animation pattern above

See Also

  • Related Components: Skeleton (content placeholder during loading), Spinner (inline loading indicator)

Last updated: February 9, 2026