Skip to content
Components

Progress

Progress for a useful, keyboard-friendly product interface.

Open ↗

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.json

Use 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 →