All blocks
Async & Statusintermediate
Approval Flow
A multi-stage submission and approval chain.
Installation
Add it with the shadcn CLI, open it in v0, or read the source.
$ npx shadcn@latest add https://stepperize.com/r/approval-flow.jsonDependencies
- @stepperize/react
- lucide-react
Requirements
- React 18 or later
- Tailwind CSS
Source
import { defineStepper } from "@stepperize/react";
import { Check, Clock } from "lucide-react";
import { useState } from "react";
const { Stepper } = defineStepper([
{ id: "submitted", title: "Submitted" },
{ id: "manager", title: "Manager" },
{ id: "finance", title: "Finance" },
{ id: "approved", title: "Approved" },
]);
export function ApprovalFlowBlock() {
const [acknowledged, setAcknowledged] = useState(false);
return (
<Stepper.Root
linear
className="w-full max-w-md rounded-xl border bg-background p-6 shadow-sm"
>
{({ stepper }) => (
<>
<div className="mb-5 flex items-center justify-between">
<div>
<p className="text-sm font-semibold">Expense #2043</p>
<p className="text-xs text-muted-foreground">$1,250 · Travel</p>
</div>
<span
className="rounded-full bg-chart-3/15 px-2.5 py-1 text-xs font-medium text-chart-3 data-[done=true]:bg-chart-2/15 data-[done=true]:text-chart-2"
data-done={stepper.isLast}
>
{stepper.isLast ? "Approved" : "Pending"}
</span>
</div>
<Stepper.List className="flex items-start">
<Stepper.Items>
{(step, index) => (
<div
key={step.id}
className="flex flex-1 items-start last:flex-none"
>
<Stepper.Item
step={step.id}
className="flex flex-col items-center gap-1.5"
>
<Stepper.Indicator className="group grid size-9 place-items-center rounded-full border-2 transition-colors data-[status=active]:border-chart-3 data-[status=active]:text-chart-3 data-[status=previous]:border-chart-2 data-[status=previous]:bg-chart-2 data-[status=previous]:text-white data-[status=upcoming]:border-border data-[status=upcoming]:text-muted-foreground">
<Check className="hidden size-4 group-data-[status=previous]:block" />
<Clock className="hidden size-4 group-data-[status=active]:block" />
<span className="text-xs font-semibold group-data-[status=active]:hidden group-data-[status=previous]:hidden">
{index + 1}
</span>
</Stepper.Indicator>
<Stepper.Title className="text-[11px] font-medium" />
</Stepper.Item>
{index < stepper.count - 1 && (
<div
className="mx-1 mt-4.5 h-0.5 flex-1 rounded bg-border data-[done=true]:bg-chart-2"
data-done={index < stepper.index}
/>
)}
</div>
)}
</Stepper.Items>
</Stepper.List>
<Stepper.Content
step={stepper.current.id}
className="mt-5 rounded-lg border bg-muted/30 p-3 text-sm text-muted-foreground"
>
{acknowledged
? "Approval acknowledged. The reimbursement is queued for payout."
: stepper.isLast
? "All approvals collected. Reimbursement scheduled."
: `Waiting on ${stepper.current.title} approval.`}
</Stepper.Content>
<Stepper.Actions className="mt-5 flex gap-2">
<button
type="button"
onClick={() => stepper.prev()}
disabled={!stepper.canPrev}
className="inline-flex h-9 items-center rounded-lg border bg-background px-4 text-sm font-medium transition-colors hover:bg-muted disabled:pointer-events-none disabled:opacity-50"
>
Send back
</button>
{acknowledged ? (
<button
type="button"
onClick={() => {
setAcknowledged(false);
stepper.reset();
}}
className="inline-flex h-9 flex-1 items-center justify-center rounded-lg bg-primary px-4 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90"
>
Restart flow
</button>
) : stepper.isLast ? (
<button
type="button"
onClick={() => setAcknowledged(true)}
className="inline-flex h-9 flex-1 items-center justify-center rounded-lg bg-primary px-4 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90"
>
Acknowledge approval
</button>
) : (
<Stepper.Next className="inline-flex h-9 flex-1 items-center justify-center rounded-lg bg-primary px-4 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:pointer-events-none disabled:opacity-50">
Approve
</Stepper.Next>
)}
</Stepper.Actions>
</>
)}
</Stepper.Root>
);
}
When to use it
Submission → review → approve chains for documents, expenses, or requests.
Accessibility
Each stage's state and actor are stated in text; action buttons are labelled.
Customization
Extend the chain with more reviewers; gate transitions on role checks in a guard.
Related blocks
Approval TimelineA vertical audit timeline where each step carries typed metadata (actor, role, SLA hours) read straight off `step` and `stepper.current` with full inference.CI/CD PipelineBuild, test, deploy, verify pipeline status with typed stage metadata.Dashboard WizardA SaaS settings wizard with sticky step nav, per-step validation, and an unsaved-changes guard wired through beforeStepChange and beforeunload.