shadcn.io is not affiliated with official shadcn/ui
API Error Dialog Block for Ark UI
A deploy platform's overview with its failed card and the error alert shown open: the 502 status, the code and request id, a retry ladder of three attempts, a real copy of the error report and the error body as a JSON tree.
Installation
Overview
Give a failure its facts and a way forward, with this API error alert for React with Ark UI. It opens from the Overview page of Keelhurst, a deploy platform: the header holds an outline Error details Button, and the page itself, a rail, three cells over a chart, is drawn under a soft scrim inside a hairline frame with a plus mark on each corner. In the drawing the Deploys cell is the failed one, a dash and the word Unavailable. The Alert Dialog lists the status as a Badge, the error code, the request id in monospace and the time in a Data List, then says how many attempts are used. Try again, the only Shine Button, counts three attempts and then says that support has the request id; Copy Button puts a typed, multi-line error report on the clipboard; Technical details opens a Collapsible with the error body in a JSON Tree View. For preview the alert is drawn open in place when the page loads; the header button opens the real Alert Dialog, with the same facts and the same count. For dashboards, deploy and build tools, and any app that talks to an API it does not own.
FAQ
Why is the alert already open, and what do I keep?
So you can see it without a click. On load the alert is a copy drawn in place over the page: a native dialog element with the alertdialog role that isn't modal, so nothing is trapped, hidden or focused, and the page around the preview keeps working. The Error details button opens the real Alert Dialog, modal, with its focus trap, Escape and focus on Close, and the copy stays closed until a reload. Both show the same ErrorFields component and share the block's state, so what was done in the copy is there in the real one. In your app keep the Alert Dialog and drop the copy: the "in-place" state, the scrim and the dialog element inside the frame.
How does the retry ladder work?
Try again starts an attempt, which takes under a second here and then fails by design, because nothing is requested; the polite line under the facts reads "Attempt 1 of 3 failed: the API answered 502 again", then 2, then 3. After the third the button goes quiet but still answers a press, naming the request id, and the line says that support has it. In your app call the failed request again in retry, stop on a success and close the alert, and keep the count so a person can't hammer an API that is down; for a 429 or a Retry-After header, wait that long before you enable the button.
Is Copy error report real?
Yes. The Copy Button writes reportOf(attempts) to the clipboard: the status and code, the request id, the endpoint, the time and the attempts so far, one fact a line, ready to paste into a ticket. A polite message says the report was copied, or that the clipboard refused it, which happens on a page that is not served over HTTPS. The request id, the endpoint and the time are typed constants here; in your app take them from the response that failed.
What are Technical details for?
They show the error body your API returned as a tree, two levels open, so a developer sees the code, the message, whether it is retryable and the upstream that timed out without leaving the alert. It sits last in the alert, so opening it never moves the facts, the attempt line or the buttons above it; in the copy on the page the alert is aligned to the top of the frame for the same reason. Values are plain foreground text, not the tree's usual colours, and the tree scrolls inside its own box if the body is long.
Where does focus go, and how does it close?
The real alert is named by its title and described by its sentence, and it opens with focus on Close, never on Try again, so a stray Enter backs out. Escape and Close return focus to Error details; clicking outside does not close it, since an alert has to be answered. Close is the focus target through initialFocusEl. The copy shown on load moves no focus; Escape inside it and Close shut it the same way.
What stays local in the demo, and how do I wire it up?
Everything. No request goes out: the 502, the request id, the time and the error body are typed constants, and Try again only counts. Closing says in a polite message how many attempts were made, and the drawn Deploys card shows "Tried 2 of 3". In your app replace the constants with the failed response, make retry call your API, and send the same report to your error tracker when someone copies it.