An approval workflow drawn as a vertical rail of nodes tinted by whether each step is done, waiting, or queued, naming the person and the timestamp, beside a tile floating the live request card.
An approval workflow drawn as a vertical rail of nodes tinted by whether each step is done, waiting or queued, naming the person and the timestamp, beside a tile floating the live request card.
Upgrade to Pro
Pro blocks install through the shadcn CLI with your license key and ship their full source. Docs and live previews stay open to everyone, so you can read every block's details first.
Swap YOUR_EMAIL and YOUR_KEY for the email and license key on your account. Find your license key on your account page.
Radix flavor
npx shadcn add "https://ui.beste.co/r/workflow48?email=YOUR_EMAIL&license_key=YOUR_KEY"Base UI flavor
npx shadcn add "https://ui.beste.co/r-base/workflow48?email=YOUR_EMAIL&license_key=YOUR_KEY"This installs the block to components/beste/block/workflow48.tsx, the notification20 request piece it floats on the tile (installed to components/beste/piece/notification20.tsx), and the badge23 and button21 components it uses for the eyebrow and the action.
The installed file exports workflow48Demo alongside the block: the exact props behind the preview above. Spread it to get a working trail in one line.
import { Workflow48, workflow48Demo } from "@/components/beste/block/workflow48";
export default function ApprovalsPage() {
return <Workflow48 {...workflow48Demo} />;
}Then replace the demo with your own props. Written out, a trimmed setup looks like this:
import { Notification20 } from "@/components/beste/piece/notification20";
import { ShieldCheck } from "lucide-react";
import { Workflow48 } from "@/components/beste/block/workflow48";
export default function ApprovalsPage() {
return (
<Workflow48
badge={{ label: "Approval chain" }}
heading="Who has to say yes, in the order they say it"
description="Access requests move through named people rather than a shared inbox."
requestLabel="Request: records access for a locum, raised 14 May at 09:04"
steps={[
{
actor: "Noah Reyes",
role: "Locum clinician",
action: "Raised the request",
detail: "Asked for the records role from inside the record he was blocked on.",
time: "09:04",
state: "done",
},
{
actor: "Automatic",
role: "Expiry",
action: "Revokes itself",
detail: "Access ends at 18:00 on the last shift, and the revocation is logged too.",
time: "17 May",
state: "queued",
},
]}
media={
<Notification20
icon={ShieldCheck}
title="Access request"
meta="Noah Reyes needs the records role until 17 May"
primaryLabel="Approve"
secondaryLabel="Decline"
/>
}
image={{ src: "/backdrops/green.jpg", alt: "Deep green gradient backdrop" }}
mediaCaption="What the approver actually sees. No admin area and no ticket."
button={{ label: "See how permissions work", href: "/security" }}
/>
);
}| Prop | Type | Default | Description |
|---|---|---|---|
badge | { label: string } | – | Eyebrow above the hairline rule, rendered through Badge23 |
heading | string | – | Section heading in the left column of the header |
description | string | – | Supporting paragraph, right-aligned from md up |
requestLabel | string | – | What this particular chain is about |
steps | Step[] | [] | The chain, in the order it happens |
media | ReactNode | – | Live asset on the sticky tile, notification20 in the demo |
image | { src: string; alt: string } | – | Backdrop behind the media tile |
mediaCaption | string | – | Caption under the tile |
button | { label: string; href: string } | – | Outline action |
className | string | – | Extra classes for the outer section |
type ActionLink = {
label: string;
href: string;
};
type TileImage = {
src: string;
alt: string;
};
type Step = {
actor: string;
role: string;
action: string;
detail: string;
time: string;
state: "done" | "waiting" | "queued";
};state tints the node: accent for done, amber for waiting, and the plain border colour for queued. Only the done state gets a check, and everything else shows a clock.border-2 border-background, which punches them out of the rail line rather than letting the line run through them. The rail itself is one absolutely positioned w-px span at left-[0.6875rem], which centres it under the size-6 nodes.actor accepts a person or the word "Automatic", which is how a policy check or an expiry sits in the same chain as a human decision without needing a separate type.time is free text, so a step that has not happened yet can read "Waiting" rather than a timestamp.last:pb-0, so the rail ends on the final node rather than trailing past it.lg:sticky lg:top-24 lg:self-start. The self-start is required, otherwise the column stretches to the row height and sticky has nothing to travel within.aria-hidden, and each step states its own state in words through time and action.workflow36
A single-rail vertical process timeline with numbered nodes and per-step duration and owner meta.
workflow49
A workflow that states one trigger on an inverted full-width panel, then forks into two equally weighted condition branches whose outcomes sit in nested cards carrying the share of cases they account for.
workflow19
Onboarding card with a progress bar and a six-step checklist. Each step shows a completion state, a title, and a short hint.
workflow26
Horizontal connected process steps with numbered nodes on a continuous rail.