Skip to main content

Progress

An accessible progress bar with determinate and indeterminate states.

import * as Progress from '@gentleduck/primitives/progress'
import * as Progress from '@gentleduck/primitives/progress'

Anatomy

<Progress.Root>
  <Progress.Indicator />
</Progress.Root>
<Progress.Root>
  <Progress.Indicator />
</Progress.Root>

Example

<Progress.Root
  className="relative h-4 w-full overflow-hidden rounded-full bg-gray-200"
  value={65}
  max={100}
>
  <Progress.Indicator
    className="h-full bg-blue-500 transition-all"
    style={{ width: `${65}%` }}
  />
</Progress.Root>
<Progress.Root
  className="relative h-4 w-full overflow-hidden rounded-full bg-gray-200"
  value={65}
  max={100}
>
  <Progress.Indicator
    className="h-full bg-blue-500 transition-all"
    style={{ width: `${65}%` }}
  />
</Progress.Root>

API

Progress.Root

Renders a <div> with role="progressbar" and appropriate ARIA attributes.

PropTypeDefaultDescription
valuenumber | nullnullCurrent value. null = indeterminate.
maxnumber100Maximum value
getValueLabel(value: number, max: number) => stringPercentageCustom label for screen readers
dir'ltr' | 'rtl'--Reading direction

Exposes data-state as indeterminate, loading, or complete, and data-value / data-max.

Progress.Indicator

Visual fill indicator. Inherits data-state, data-value, and data-max from context.

Indeterminate state

<Progress.Root value={null} className="relative h-4 w-full overflow-hidden rounded-full bg-gray-200">
  <Progress.Indicator className="h-full bg-blue-500 animate-indeterminate" />
</Progress.Root>
<Progress.Root value={null} className="relative h-4 w-full overflow-hidden rounded-full bg-gray-200">
  <Progress.Indicator className="h-full bg-blue-500 animate-indeterminate" />
</Progress.Root>
@keyframes indeterminate {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(400%); }
}
.animate-indeterminate {
  width: 30%;
  animation: indeterminate 1.5s ease-in-out infinite;
}
@keyframes indeterminate {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(400%); }
}
.animate-indeterminate {
  width: 30%;
  animation: indeterminate 1.5s ease-in-out infinite;
}