Progress
Progress for a useful, keyboard-friendly product interface.
Make it yours.
Install the editable source and its dependencies into your React + Tailwind project.
Scroll horizontally for long lines
pnpm dlx shadcn@4.0.8 add http://localhost:3000/r/progress.jsonUse it
Scroll horizontally for long lines
"use client";
import {useState,useEffect} from 'react';
import {Progress} from '@/components/jez-ui/ui/progress';
import {Button} from '@/components/jez-ui/ui/button';
import {Pause} from 'lucide-react';
import {Play} from 'lucide-react';
import {RotateCcw} from 'lucide-react';
export default function Example(){
const [progress,setProgress]=useState(0);const [progressRunning,setProgressRunning]=useState(false);useEffect(()=>{if(!progressRunning)return;if(progress>=100){setProgressRunning(false);return;}const timer=setTimeout(()=>setProgress(v=>Math.min(100,v+4)),200);return()=>clearTimeout(timer);},[progressRunning,progress]);
return <><div className="w-full max-w-sm"><div className="mb-7"><h3 className="text-2xl tracking-tight">Getting there.</h3><p className="mt-2 text-sm leading-relaxed text-muted-foreground">The next release is taking shape.</p></div><div className="grid gap-4"><div className="flex items-center justify-between text-sm"><span>{progress===100?"Export complete":progressRunning?"Preparing export…":progress>0?"Export paused":"Ready to export"}</span><span className="font-mono tabular-nums">{progress}%</span></div><Progress value={progress} label="Export progress" showLabel={false}/><div className="flex gap-2"><Button size="sm" disabled={progress===100} onClick={()=>setProgressRunning(v=>!v)}>{progressRunning?<Pause size={14}/>:<Play size={14}/>} {progressRunning?"Pause":progress>0?"Resume":"Start export"}</Button><Button size="sm" variant="ghost" aria-label="Reset progress" onClick={()=>{setProgressRunning(false);setProgress(0);}}><RotateCcw size={14}/></Button></div><p className="text-xs text-muted-foreground">Simulated export · try pausing halfway through.</p></div></div></>;
}Props & types
Scroll horizontally for long lines
{
value: number;
label?: string;
className?: string;
showLabel?: boolean;
}Source
View 2 source files
registry/ui/progress.tsx
Scroll horizontally for long lines
"use client";
import * as React from "react";
import { cn } from "./utils";
export function Progress({
value,
label = "Progress",
className,
showLabel = true,
}: {
value: number;
label?: string;
className?: string;
showLabel?: boolean;
}) {
const v = Number.isFinite(value) ? Math.max(0, Math.min(100, value)) : 0;
return (
<div className={cn("grid w-full min-w-0 gap-2", className)}>
{showLabel && (
<div className="flex justify-between text-sm">
<span>{label}</span>
<span className="tabular-nums">{Math.round(v)}%</span>
</div>
)}
<div
role="progressbar"
aria-label={label}
aria-valuenow={v}
aria-valuemin={0}
aria-valuemax={100}
className="h-2 overflow-hidden rounded-full bg-muted"
>
<div
className="h-full origin-left rounded-full bg-primary transition-transform duration-500 ease-out motion-reduce:transition-none"
style={{ transform: `scaleX(${v / 100})` }}
/>
</div>
</div>
);
}
registry/ui/utils.ts
Scroll horizontally for long lines
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...values: ClassValue[]) {
return twMerge(clsx(values));
}
Dependencies
clsx@2.1.1 · tailwind-merge@3.5.0
Accessibility & behaviour
Provide meaningful labels and preserve keyboard focus styles. Check contrast when customising theme colours.
Read the accessibility and performance guide →