shadcn.io is not affiliated with official shadcn/ui
Card With Actions Skeleton Block for Radix UI
An expense card awaiting approval that loads in place: the title, status, amount, submitter and memo come in over bars, then Approve really turns the badge to Approved while Decline and Snooze say they are not wired.
Installation
Overview
Show a card with actions while it loads, then swap the content in without moving anything, with this card with actions skeleton for React with Radix UI. A hairline frame with a plus mark on each corner holds an expense from Batiste, an expenses tool, in three bands: the title and status, the facts and the actions. The labels (Amount, Submitted by, Category, Memo) and the three buttons are real from the first paint; the title, the status Badge, the three facts and the two-line memo are Skeleton bars that become the request in the same place. Once it is in, Approve, the card's one Shine Button, turns the badge to Approved in place and the polite line says nothing was sent; Decline and Snooze are plain Buttons that say they are not wired in this demo. It's built from Skeleton, Skeleton Text, Badge, Shine Button, Button and Guides, with a CSS entrance only. For approval requests, review queues and any card with a decision.
FAQ
What stays local, and how do I wire it to a real query?
Nothing is fetched. The expense is typed in EXPENSE, and Approve only changes a flag in the tab. Load starts a 700 millisecond timer, and when it ends the phase becomes ready and the data swaps in; Reload goes back to the bars and in again. To wire it for real, delete the timer, the Load button and the polite line, and set data-state (loading or ready) and aria-busy on the card from your query's pending state, or use the bars as a Suspense fallback and render the real content when it arrives. Approve is a local result here; in your app, send the decision, then set the badge from the response, and show a Problem line if it fails. Keep one component for the bars and the data so they can't drift apart.
Why doesn't the card jump when the data arrives?
Every value shares a grid cell with its bar, the status badge has a fixed w-36 box so "Awaiting approval" and "Approved" are the same width, and the memo's bars and words share one cell. The three buttons are real in both phases and wrap onto a second line on a phone in the same way every time. The polite line reserves two lines, so the card is exactly as tall before, during, after loading and after approving.
What does Approve do, and what resets it?
Once the expense is loaded, Approve sets a flag: the badge changes from "Awaiting approval" to "Approved" with a check, the button becomes unavailable and the polite line says "Approved in this demo. Nothing was sent." Reload goes back to the bars and the request is awaiting approval again. Before the data is in, the three buttons are aria-disabled, not disabled, so they keep their place in the tab order and focus never moves.
How is the status shown without colour?
By a word and a shape: "Awaiting approval" in a secondary badge, then "Approved" with a check in an outline badge. No status colour is used, since the state is already in the text. If you add a rejected state, give it its own word and icon in the same fixed-width badge.
Is it accessible?
The card is marked busy while it loads and carries a screen-reader-only "Loading expense…", and the polite line announces what loaded once. The bars are hidden from assistive technology and the layer that isn't showing is invisible, so it is neither read nor focusable. The buttons are unavailable, not hidden, so their labels are read from the start. The pulse stops when the data is in, and under reduced motion there is no pulse, no spinner and no stagger: the swap is instant.