ComponentsCSS
Progress
Slim track whose gradient fill eases to each new value, in accent or a status tone.
Cloning8%
marketing-site · main · 4f2a91c
Installation
npx shadcn@latest add https://ui-collections.vercel.app/r/glass/progress.jsonAlso installs the theme, cn(). Packages: clsx, tailwind-merge, radix-ui.
Usage
progress-demo.tsx
"use client";
import { useEffect, useState } from "react";
import { Progress } from "@/registry/kit/ui/progress";
const steps = ["Cloning", "Installing", "Building", "Uploading", "Ready"];
export default function ProgressDemo() {
const [value, setValue] = useState(8);
useEffect(() => {
const timer = setInterval(() => setValue((v) => (v >= 100 ? 8 : Math.min(100, v + 12))), 900);
return () => clearInterval(timer);
}, []);
const step = steps[Math.min(steps.length - 1, Math.floor((value / 100) * (steps.length - 1)))];
return (
<div className="surface grid w-full max-w-sm gap-3 rounded-card p-6">
<div className="flex justify-between text-sm">
<span id="build-label">{step}</span>
<span className="text-muted tabular-nums">{value}%</span>
</div>
<Progress value={value} aria-labelledby="build-label" />
<p className="text-xs text-muted">marketing-site · main · 4f2a91c</p>
</div>
);
}API
value= 0
numberCurrent value.
max= 100
numberValue at full.
tone= "accent"
"accent" | "success" | "warning" | "danger" | "info"Fill colour; every tone fades in from the left.
Source
components/ui/progress.tsx
"use client";
import { Progress as ProgressPrimitive } from "radix-ui";
import type { ComponentProps, CSSProperties } from "react";
import { cn } from "@/registry/kit/lib/utils";
export type ProgressTone = "accent" | "success" | "warning" | "danger" | "info";
export interface ProgressProps extends ComponentProps<typeof ProgressPrimitive.Root> {
/** Fill colour; every tone uses the same light-to-full gradient. */
tone?: ProgressTone;
/** Extra classes for the fill. */
indicatorClassName?: string;
}
/** Slim track whose fill eases to each new value. */
export function Progress({ className, tone = "accent", indicatorClassName, value = 0, max = 100, ...props }: ProgressProps) {
const percent = Math.min(100, Math.max(0, ((value ?? 0) / max) * 100));
return (
<ProgressPrimitive.Root
value={value}
max={max}
className={cn("relative h-2 w-full overflow-hidden rounded-full bg-fg/10 shadow-[inset_0_1px_2px_rgb(0_0_0/0.2)]", className)}
{...props}
>
<ProgressPrimitive.Indicator
className={cn(
// The fill is slid in from the left, so the gradient starts where it becomes visible:
// every bar fades light to full over its own length, however short.
"h-full w-full rounded-full bg-[linear-gradient(90deg,transparent_var(--from),color-mix(in_oklab,var(--bar)_45%,transparent)_var(--from),var(--bar))] transition-[translate] duration-700 ease-theme",
indicatorClassName,
)}
style={{ translate: `${percent - 100}% 0`, "--bar": `var(--color-${tone})`, "--from": `${100 - percent}%` } as CSSProperties}
/>
</ProgressPrimitive.Root>
);
}