Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Update Card Modal Billing Block for Radix UI

A payment method panel with its card update shown open: a card form with Luhn-checked number, expiry and CVC, a bank confirmation with a six-slot code and a timed resend, and a confirmation that the new card is the default.

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

Replace a card in three short steps, with this update card dialog for React with Radix UI. It opens from the Payment method panel of Lingoloft, a language-learning app: the header states the card on file and holds an outline Update card Button, and the panel itself, a card row, a backup row and a short history, is drawn under a soft scrim inside a hairline frame with a plus mark on each corner. The Dialog starts with the card form: the number groups itself as you type and is checked with the Luhn sum, the expiry and CVC each carry their own message, and the test number is named. Continue moves to "Confirm with your bank": a six-slot Input OTP that checks itself when full, the demo code shown, and a Resend held for 30 seconds by a Timer. A right code shows that the new card is now the default and Done puts it in the panel. The one Shine Button is Continue, then Confirm card, then Done. For preview the dialog is drawn open in place when the page loads; the header button opens the real Dialog with the same fields and state. For payment settings, expiring cards and any subscription app.

FAQ

Why is the dialog already open, and why is the clock stopped?

So you can see it without a click. On load the dialog is a copy drawn in place over the panel: a native dialog element that isn't modal, so nothing is trapped, hidden or focused, and the page around the preview keeps working. A countdown would age the preview, so in the copy the Timer is stopped at 30 seconds. The Update card button opens the real Dialog, modal, with its focus trap, Escape and focus return, and there the clock runs from open. The copy stays closed until a reload. Both show the same CardFields component and share the block's state, so what you typed in the copy is there in the real one. In your app keep the Dialog and drop the copy: the "in-place" state, the scrim and the dialog element inside the frame.

Can I use these card fields in production?

No. They show the pattern: the number is grouped and checked in the tab's memory (the brand from the first digits, the Luhn sum, the expiry against a fixed date), autocomplete is off so a browser never offers a real saved card to a demo, and only "Visa ending 4242" and the expiry are kept. The number and the CVC are wiped the moment the form is accepted and are never drawn back. A real page never lets card digits reach its own code: use the fields your payment provider hosts, in an iframe of theirs inside this layout. The test number 4242 4242 4242 4242 passes with any future expiry.

What is the bank confirmation?

Many banks ask the cardholder to confirm a new card with a code, usually through 3-D Secure. Here step two stands in for it: the demo code is 123456, shown under the slots because nothing is sent. Type or paste it and it checks itself; a wrong code marks the slots, says so and clears them. Resend is held for 30 seconds by the Timer and restarts it. In your app the provider opens the bank's own step and tells your page the result; do not build the confirmation yourself.

What stays local, and how do I wire it up?

Everything. Continue validates the form and moves on, Confirm card accepts the demo code, and Done updates the card line in the header and the badge in the drawn panel, with a polite status message that nothing was sent. In your app create the update with your payment provider, let it collect the card and run the bank step, and set the card line from what it returns. Nothing in this page calls a network.

Where does focus go?

The real dialog is named by its title, which follows the step. Focus starts in the card number, then moves where the step needs it: to the expiry once a number checks out, to the CVC once the expiry is whole, to the first code slot at the bank step and to the result when it is done. Escape, Cancel and the close button return focus to the Update card button. The trigger is the Update card button wrapped in DialogTrigger with asChild, and the dialog is controlled with open and onOpenChange.

How does it look on a phone?

The copy shown on load docks to the foot of the frame as a bottom sheet, its actions pinned at the bottom, and the six slots shrink a little so they fit at 320 pixels. The real dialog keeps the Dialog's own phone layout, a sheet from the bottom that scrolls inside itself.