Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Expense Approval Queue CRUD Block for Svelte

An expense desk where requests over 500 dollars need two approvers: tabs with counts, a reason required to reject, no approving your own request and a dialog for new ones.

Framework
UI library

Preview

Preview width

Installation

Part of Pro
Pro unlocks the code and one-line install of every Pro button and text effect, plus the MCP for your AI editor.
Have Pro already? Sign in to open it.Opens here as soon as you're Pro.

Overview

Add a rule to an approval list and show it, with this expense queue for Svelte with Ark UI. Tabs for Pending, Approved and Rejected carry their counts and re-derive as requests move. A typed rule is shown: over $500 needs two approvers, anything else one, so a row reads "1 of 2 approved" with the approvers' initials in Badges. Approve is a row button, and the first one that can be pressed is the one Shine Button; Reject opens a Popover with a required reason; you cannot approve your own request ("You can't approve your own request", aria-disabled with the reason in the row) or approve twice. Withdraw removes your own pending request. New request, an outline button in the header, opens a Dialog with a what-for and an amount that says how many approvals it needs. A table from the medium breakpoint, cards below, and Undo for every change. No bulk actions: those are in the reviews and tables blocks.

FAQ

How do I change the approval rule?

Edit THRESHOLD and needOf. Today needOf returns 2 above 500 dollars and 1 otherwise; the row text, the dialog's hint ("$1,240 needs 2 approvals") and the status transitions all call it, so a rule such as three approvers over 5,000 is one more line.

Why can't I approve my own request?

Requests you create carry own: true, and canApprove refuses them. Their Approve button is aria-disabled and stays focusable; pressing it says why in the polite line. Instead of Reject they show Withdraw, which removes the request and offers Undo.

What does Reject need?

A reason. The popover will not send an empty one: it shows the problem, keeps focus in the field and leaves the request pending. A rejected request moves to the Rejected tab with its reason shown in the row, and Undo puts it back to Pending.

How do I fill it with my requests?

Edit REQUESTS: each has an id, a title, a whole-dollar amount, a requester, approvals (initials of who approved, "You" for the viewer), a status and, when rejected, a reason. Counts, progress text and which button is the primary are derived.

What stays local, and how do I wire real approvals?

Requests live in state in this tab and a reload brings the eight originals back; nothing is sent to anyone. In your app call your API from approve, reject, withdraw and create, enforce the two-approver rule and the no-self-approval rule on the server, and make Undo a compensating call.

How does it look on a phone?

Each request becomes a card: title, requester and amount on top, the approvals under them, then the buttons in one row. The tabs keep their counts (hidden below 22rem, where the accessible names still carry them) and the list keeps its height.