Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Payment Failed Checkout Block for React Aria

A failed-payment page with real recovery: the bank's reason in words, an attempts line for the card, the order hold time, a choice of saved cards, and a 30-second wait before the next try once the limit is reached.

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

Recover a declined payment without sending anyone back to the start, with this payment-failed checkout for React with React Aria. A compact header from Nightfern, a sleepwear shop, sits over a hairline frame. The first band is the order (a sleep set at $86.00) and the time it is held until, "Fri 16 Oct, 18:00 UTC". The next says what happened in words, the bank's reply with its code (05, do not honour), and an outline Badge with the attempts remaining, "2 of 3 attempts remaining". Then the real choices as native radio rows with a two-letter tile each: try Visa ending 4242 again, pay with another saved card, or add a new one through your provider. In this demo the declined card fails every time, so the third failure reaches the limit: that card is locked and struck through, the choice moves to another card and a 30-second wait starts, shown as a Countdown Button in the pay button's place. One Shine Button shows at a time. For any checkout where a card can be declined.

FAQ

Why is there a limit on attempts?

Because repeated declines can lock the card or look like fraud to the bank, and the shop should stop before that. The demo allows 3 tries on the declined card, counts them in the attempts badge, and then locks that card for the order. A real limit would be your payment provider's guidance or your own risk rule.

What is the 30-second wait?

It starts when the limit is reached and holds the pay button's place, counting down in its label and draining a fill behind it. Pressing is not possible while it counts, focus stays where it was, and it is a wait the visitor started, not a manufactured deadline. When it ends the pay button returns with the next card selected.

What are the recovery choices?

Try the same card again while it has attempts, pay with another saved card (which succeeds in the demo), or add a new card, which in a real shop opens your payment provider's fields. The demo does not draw card fields here and says so when you choose it; the minimal checkout block shows that form.

Is the order hold real?

No: "held until Fri 16 Oct, 18:00 UTC" is typed text. In your app it would be the time your server holds the stock, and the paragraph above the radios should show the same time your server uses.

How is the failure announced?

The reason is a heading that takes focus after each failed attempt, so a screen reader hears the bank's reply and the attempts remaining; one polite status line says what changed. The radios are a named group and the locked card is disabled, struck through and explained in its own row.

What stays local, and how do I connect it for real?

All of it: the cards, the bank's reply and the outcome of each attempt are typed in the demo, and nothing is charged. To connect it, show the decline reason your provider returns, count attempts per order on your server, create each retry as a new payment attempt on the same order, and keep the hold until it expires.