Shows and acts on a multi-stage approval: sequential and parallel stages ("all of" / "any of"), approvers with decisions, timestamps and comments, rule conditions, SLA deadlines and delegation. ApprovalActions confirms each decision in a dialog with a note; ApprovalSummary is the one-line status for table rows.
Submitted by Deniz Aydın
Annual leave, 17–28 November (10 working days).
Manager approval
Approved1 approver
Ayşe YılmazEngineering manager
approved
Fine by me — Mert covers the on-call week.
HR review
In reviewAny one of 2 approversDue in 2 days
Selin ArslanPeople partner
Pending
Burak KoçPeople operations
Pending
Payroll update
Waiting1 approver
Gizem ErPayroll
—
Installation
The whole kit as one package — get updates by bumping the version. Add the styles to globals.css: @import "@vamos-ui/react/styles.css";
Usage
import { ApprovalFlow, ApprovalActions, ApprovalSummary } from "@vamos-ui/react/business";
<ApprovalFlow requester={{ name: "Deniz Aydın", at: request.createdAt }} stages={request.stages} />
<ApprovalActions canAct={me.isApprover} onApprove={approve} onReject={reject} onRequestChanges={sendBack} />
<ApprovalSummary stages={row.stages} /> // "2 of 3 approved · Now: Finance"The flow is an <ol>; parallel stages are a nested list named "In parallel". Status is always text (badge + decision words), never colour alone. A visual flow designer is out of scope: the server defines stages, this renders and acts on them.
Examples
Parallel branches
Consecutive stages with the same parallelGroup render side by side. Each says "Any one of 2 approvers" or "All of 3 approvers"; delegated decisions carry a badge.
In parallel
Finance
ApprovedAny one of 2 approvers
Kerem Demir
approved
Elif Şahin
—
Legal
In reviewAll of 3 approversDue tomorrow
Zeynep Kaya
approved
Liability cap is acceptable.
Oliver GrantExternal counsel Delegated
approved · on behalf of Hannah Weiss
Mehmet Uçar
Pending
CEO signature
Waiting1 approver
Can Öztürk
—
Conditions and changes requested
A Director stage added by a rule ("Amount > $10,000") sent the request back; later stages are skipped.
Submitted by Jonas Fischer
Budget owner
Approved1 approver
Nora Lindqvist
approved
Director
Changes requested1 approverOnly when Amount > $10,000
Rafael Duarte
requested changes
Please attach the second supplier quote before I sign this.
Finance
Skipped1 approver
Priya Raman
—
Approve, reject, request changes, delegate
Reject and Request changes require a note (requireNoteOn); the dialog focuses it. Results are announced politely.
Quote Q-2026-207 · Northwind Traders
18% discount on 120 seats — requested by Pınar Güneş
Sales manager
In review1 approverOnly when Discount > 15%
YouSales manager
Pending
CFO
Waiting1 approverOnly when Discount > 25%
Hana Kobayashi
—
In a table row
ApprovalSummary: one dot per stage plus "2 of 3 approved" — expense and leave lists.
| Expense | Employee | Amount | Approval |
|---|---|---|---|
| EXP-2041Client dinner, Izmir | Ayla Çelik | $412.80 | 1 of 3 approved · Now: Finance |
| EXP-2040Flights IST → LHR | Ben Hartley | $1,284.00 | 3 of 3 approved |
| EXP-2038Conference ticket | Farah Jansen | $899.00 | Rejected |
| EXP-2035Taxi, airport transfer | Omar Ekström | $64.20 | Changes requested |
API
ApprovalFlow
| Prop | Type | Default |
|---|---|---|
stages{ id, title, mode: "all" | "any", condition?, approvers[], status, dueAt?, parallelGroup? }. Status: waiting · active · approved · rejected · changes_requested · skipped · withdrawn. | ApprovalStageData[] | — |
requesterFirst node: who submitted, when and why. | { name; avatarUrl?; at; note? } | — |
orientationHorizontal lays stages out in a scrollable row. | "vertical" | "horizontal" | "vertical" |
variantcompact renders ApprovalSummary. | "full" | "compact" | "full" |
nowReference time for SLA (pass it for SSR-stable output). | string | number | Date.now() |
labelsStatuses, mode texts (plural-aware), SLA, actions. | Partial<ApprovalFlowLabels> | — |
ApprovalApprover / ApprovalDecision
| Prop | Type | Default |
|---|---|---|
approverOne person on a stage. | { id; name; avatarUrl?; role?; decision? } | — |
decisionUTC ISO instant; the note renders as a comment. | { value: "approved" | "rejected" | "changes_requested"; at; note?; delegatedFrom? } | — |
ApprovalActions
| Prop | Type | Default |
|---|---|---|
canActWhen false, shows cantActReason instead of buttons. | boolean | — |
onApproveApprove with an optional note. | (note?: string) => Promise<void> | void | — |
onRejectReject; a note is required by default. | (note: string) => Promise<void> | void | — |
onRequestChangesShows "Request changes". | (note: string) => Promise<void> | void | — |
onDelegate / delegatesShows "Delegate" with a person Select. | (to, note?) => … / { id; name }[] | — |
requireNoteOnWhich actions need a note. | ("approve" | "reject" | "changes")[] | ["reject", "changes"] |
cantActReasonText for viewers who can't act. | string | — |
sizeButton size. | "sm" | "md" | "md" |
ApprovalSummary
| Prop | Type | Default |
|---|---|---|
stagesSame data as the flow. | ApprovalStageData[] | — |
showCurrentAppend "Now: {stage}". | boolean | true |