# V progress 15

> Build pipeline stages

A shadcn/ui block from the [@cnippet](https://ui.cnippet.dev/) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/cnippet/v-progress-15).

- Page, with a live preview: https://shadcn.exchange/r/cnippet/v-progress-15
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/cnippet/v-progress-15-light-25eae0f52a1fcabb.png
- Registry item: https://ui.cnippet.dev/r/v-progress-15.json
- Categories: progress
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://ui.cnippet.dev/r/v-progress-15.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `@cnippet/progress`

## Files

### `registry/default/variants/v-progress-15.tsx`

```tsx
import { CheckIcon, CircleDotIcon, ClockIcon } from "lucide-react";
import {
  Progress,
  ProgressIndicator,
  ProgressTrack,
} from "@/registry/default/ui/progress";

type StageStatus = "done" | "active" | "pending";

const stages: { label: string; status: StageStatus; time: string }[] = [
  { label: "Checkout", status: "done", time: "3s" },
  { label: "Install", status: "done", time: "42s" },
  { label: "Build", status: "active", time: "1m 12s" },
  { label: "Test", status: "pending", time: "—" },
  { label: "Deploy", status: "pending", time: "—" },
];

const done = stages.filter((s) => s.status === "done").length;

const stageIcon: Record<StageStatus, React.ReactNode> = {
  active: (
    <CircleDotIcon
      aria-hidden="true"
      className="size-4 animate-pulse text-primary"
    />
  ),
  done: <CheckIcon aria-hidden="true" className="size-4 text-success" />,
  pending: (
    <ClockIcon aria-hidden="true" className="size-4 text-muted-foreground" />
  ),
};

export function Pattern() {
  const value = (done / stages.length) * 100;

  return (
    <div className="w-full max-w-xs space-y-4">
      <div className="flex items-center justify-between">
        <span className="font-medium text-sm">Build pipeline</span>
        <span className="text-muted-foreground text-xs">
          {done}/{stages.length} stages
        </span>
      </div>
      <Progress value={value}>
        <ProgressTrack>
          <ProgressIndicator />
        </ProgressTrack>
      </Progress>
      <div className="space-y-2">
        {stages.map(({ label, status, time }) => (
          <div className="flex items-center gap-3" key={label}>
            {stageIcon[status]}
            <span
              className={`flex-1 text-sm ${status === "pending" ? "text-muted-foreground" : ""}`}
            >
              {label}
            </span>
            <span className="text-muted-foreground text-xs tabular-nums">
              {time}
            </span>
          </div>
        ))}
      </div>
    </div>
  );
}
```
