Progress

Show how much of a known task is complete.

statusloading
Preview
Progress preview
Migrating components64%

When to use

  • Determinate progress for uploads, imports, or long tasks.
  • Progress with a visible percentage and a check at completion, via showValue.

When not to use

  • Use skeleton while content is loading with no progress to report.
  • Use usage-meter for quota against a limit, and gauge for dashboard metrics.
  • Use stepper to show position in a multi-step flow.

shadcn CLI

Also installs (1)

  • manicat-foundation
Manual install(+)
registry.jsonjson
{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "progress",
  "type": "registry:ui",
  "title": "Progress",
  "description": "Show how much of a known task is complete.",
  "categories": [
    "feedback"
  ],
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "registryDependencies": [
    "https://manicat.viraza.net/r/manicat-foundation.json"
  ],
  "files": [
    {
      "path": "registry/components/progress/progress.tsx",
      "type": "registry:component",
      "target": "@components/manicat/progress/progress.tsx"
    },
    {
      "path": "registry/components/progress/progress.module.css",
      "type": "registry:component",
      "target": "@components/manicat/progress/progress.module.css"
    }
  ],
  "docs": "Docs and live preview: https://uiarc.dev/components/progress",
  "meta": {
    "tier": "free",
    "kind": "component",
    "docs": "https://manicat.viraza.net/components/progress",
    "markdown": "https://uiarc.dev/components/progress/markdown",
    "tags": [
      "status",
      "loading"
    ]
  }
}

Usage

Install the component, then copy this into your app. Change the content and the props to fit your screen.

example.tsxtsx
"use client";

import { useState } from "react";
import { Progress } from "@/components/manicat/progress/progress";
import { Button } from "@/components/manicat/button/button";

export function ProgressExample() {
  const [value, setValue] = useState(64);

  return (
    <div className="grid w-full max-w-sm gap-4">
      <Progress label="Migrating components" value={value} showValue />
      <div className="flex gap-2">
        <Button size="sm" variant="secondary" onClick={() => setValue((current) => Math.max(0, current - 20))}>
          -20
        </Button>
        <Button size="sm" onClick={() => setValue((current) => Math.min(100, current + 20))}>
          +20
        </Button>
      </div>
    </div>
  );
}

Examples

Real screens rather than isolated props. Switch to Code to copy a whole example into your project.

Upload progress A controlled progress bar driven by a timer, the way an upload or an import would drive it.

Upload progress A controlled progress bar driven by a timer, the way an upload or an import would drive it. preview
Uploading brand-assets.zip0%

Accessibility

  • Renders role="progressbar" with aria-valuemin, aria-valuemax, aria-valuenow, and a percentage aria-valuetext.
  • Label changes crossfade; outgoing copies are aria-hidden while they fade.
  • It is not a live region; announce completion separately if it matters.

Motion

  • One smooth spring drives both the fill and the counted number, so they always agree. The fill slides in from the left to keep its rounded end.
  • At 100% the fill turns to the success colour and a check settles in beside the count.
  • Reduced motion jumps the fill and count to the new value.

Responsive behavior

  • The bar fills its container width, so it works in cards, rows, and full-width layouts.

Performance

  • One spring drives both the fill and the counted number; no observers or timers.
  • It is not a live region, so announce completion separately if it matters.

Notes for AI

  • Use for determinate task progress. Use skeleton while content has no progress to report, gauge or activity-rings for dashboard metrics, and usage-meter for quota against a limit.
  • Pass raw value and max; the percentage is computed and clamped for you.