Approval Chain Trail

Approval chain

Who has to say yes, in the order they say it

Access requests move through named people rather than a shared inbox, and every decision writes itself into the audit log as it happens.

Request: records access for a locum, raised 14 May at 09:04

Raised the request

09:04

Noah Reyes · Locum clinician

Asked for the records role for the three days he is covering, from inside the record he was blocked on.

Checked it against policy

09:04

Automatic · Policy check

Confirmed the dates fall inside his booked shifts and that the role expires when they end.

Approved

09:19

Elena Rourke · Practice lead, record owner

Approved from the notification without opening the admin area. The whole decision took eleven seconds.

Counter-signing

Waiting

Dr Amelia Frost · Clinical governance

Needed only where clinical notes are involved. She has until the shift starts, and is reminded once.

Revokes itself

17 May

Automatic · Expiry

Access ends at 18:00 on the last shift. Nobody has to remember, and the revocation is logged too.

Deep green gradient backdrop

Access request

Noah Reyes needs the records role until 17 May

What the approver actually sees. No admin area, no ticket, and no way to approve something without it being recorded.

See how permissions work
About this block

Approval Chain TrailPRO

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.

Workflow48: Approval Chain Trail

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.

Upgrade Now

Installation

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

bash
npx shadcn add "https://ui.beste.co/r/workflow48?email=YOUR_EMAIL&license_key=YOUR_KEY"

Base UI flavor

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

Quick start

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.

tsx
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:

tsx
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" }}
    />
  );
}

Props

PropTypeDefaultDescription
badge{ label: string }Eyebrow above the hairline rule, rendered through Badge23
headingstringSection heading in the left column of the header
descriptionstringSupporting paragraph, right-aligned from md up
requestLabelstringWhat this particular chain is about
stepsStep[][]The chain, in the order it happens
mediaReactNodeLive asset on the sticky tile, notification20 in the demo
image{ src: string; alt: string }Backdrop behind the media tile
mediaCaptionstringCaption under the tile
button{ label: string; href: string }Outline action
classNamestringExtra classes for the outer section
ts
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";
};

Behavior notes

  • 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.
  • Nodes carry 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.
  • The last step drops its bottom padding with last:pb-0, so the rail ends on the final node rather than trailing past it.
  • The tile column is 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.
  • Node markers are aria-hidden, and each step states its own state in words through time and action.

More Workflow blocks

View all Workflow
PRO

workflow49

Branching Decision Flow

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.

PRO

workflow36

Process Timeline

A single-rail vertical process timeline with numbered nodes and per-step duration and owner meta.

PRO

workflow26

Process Flow

Horizontal connected process steps with numbered nodes on a continuous rail.

PRO

workflow19

Workflow Onboarding Checklist

Onboarding card with a progress bar and a six-step checklist. Each step shows a completion state, a title, and a short hint.

PRO

workflow38

Cascade Process

Process steps cascading in a diagonal staircase with connecting markers.

PRO

workflow27

Process Ascent

Ascending-staircase process timeline where each phase climbs above the last, with oversized index numerals, cadence labels and per-phase deliverables.