shadcn.io is not affiliated with official shadcn/ui
Minimal Checkout Block for Ark UI
The smallest checkout: a one-line order, an email, a card as one joined control with a brand tile and Luhn check, and a Pay button that names each wrong part, waits a moment and keeps only the brand and last four digits.
Installation
Overview
Sell one thing with nothing in the way, with this minimal checkout for React with Ark UI. A compact header from Tern and Tide, a type foundry, sits over a narrow hairline frame with a plus mark on each corner. The first band is the whole order in one line: Tidewater Sans, one studio licence, $39.00. Below it an email Input in a Field takes the receipt address, and the card is one joined control built from Input Group: number, expiry and CVC in a single bordered group with one focus ring, a brand tile and the brand's name from the first digits, the number grouped as you type (4-4-4-4, or 4-6-5 for Amex), and a complete part moving focus to the next. A press with problems names every wrong part and moves focus to the first; a clean press waits a moment (the button keeps its size) and then replaces the fields with the result, "Visa ending 4242", and says that nothing was charged. Pay $39.00 is the only Shine Button. It's built from Input, Input Group, Field, Button, Shine Button and Guides, with a CSS entrance only. For one-product shops, licences, tickets and any checkout that is one line long.
FAQ
Can I use this to take real card payments?
No, and the code says so. It checks a card in the tab's memory (brand by the first digits, the Luhn sum, the expiry against a fixed month) so you can see the pattern. A real page never lets card digits reach its own code: it uses the fields your payment provider hosts, an iframe of theirs, and this layout, labels and error wording around them, with the real autocomplete tokens (cc-number, cc-exp, cc-csc, cc-name) that those fields set themselves. Autocomplete is off in the demo so a browser never offers a real saved card to it. Nothing here is sent, stored or charged.
Which cards work in the demo?
4242 4242 4242 4242 with any future expiry (checked against October 2026) and any three digits passes; the Use the test card button fills it. 4000 0000 0000 0002 passes the card check but is declined after the wait, to show that path: the group says the card was declined and focus returns to the number. A number that fails the Luhn sum, an expiry in the past, a month above 12 or a CVC of the wrong length each says so under the group and marks that part invalid.
What happens when I press Pay with something wrong?
The button is never disabled. A press marks every wrong field, says "Not paid. Fix the marked fields." in the polite status line and moves focus to the first wrong one, in reading order: email, number, expiry, CVC. Each field's problem has its own reserved line with an icon and words, so nothing below it moves, and an error that was shown follows your typing and clears when the part is fixed.
What does a successful payment keep?
Only the brand and the last four digits ("Visa ending 4242") and the email you typed. The number, expiry and CVC are cleared from state the moment the demo answers, and the fields are replaced in place by the result with a Pay again button, so the panel never changes height. Focus moves to the result. The sentence in the status line says "Paid in this demo. Nothing was charged." and names where the receipt would go.
How do auto-advance and Backspace work?
When the number reaches its brand's length and passes the Luhn sum, focus moves to the expiry, and when the expiry is a valid future month, to the CVC. Only typing at the end advances, so editing the middle never jumps. Backspace in an empty expiry or CVC goes back to the part before it with the caret at its end, and the caret stays with its digits while the number is regrouped. Pasting works too: spaces and slashes are ignored.
What stays local, and how do I connect it for real?
Everything: the wait is a 700 ms timer and the answer is decided by the card number you typed. To connect it, mount your provider's hosted number, expiry and CVC fields in the group's place, keep the email field, and have Pay create the payment on your server with the provider's token (never the digits). Show the provider's error in the same slot the card problems use, and take the receipt address from the email field.