shadcn.io is not affiliated with official shadcn/ui
Promo Code Checkout Block for React
One promo code with a replace check: a Have a code disclosure that accepts pasted codes, an applied chip with its effect, an in-place Replace A with B question with both totals, named reasons when a code is unknown, ended or under the minimum, and a summary that re-adds in cents.
Installation
Overview
Take a discount code without the usual guessing, with this promo code checkout for React with Base UI. A compact header from Knoll and Fern, a homeware shop, sits over a hairline frame with a plus mark on each corner. The main cell opens with "Have a code?", a disclosure button that shows or hides a Field with a code Input and an Add button; it accepts what people paste (spaces and dashes dropped, capitals raised). An accepted code becomes a chip with its effect ("AUTUMN15: 15% off, $11.40") and a Remove that names it. Adding a second code asks in place, "Replace AUTUMN15 with SHIP5?", with both totals side by side and Keep or Replace as the answer. A code that cannot apply says why in the field's own line: not a code we know, ended on a typed date, or a minimum spend the items don't reach. The summary beside it re-adds the order in whole cents on every change. Continue to payment is the only Shine Button. It's built from Field, Input, Button and Guides. For stores that allow one code per order.
FAQ
What does it accept?
Whatever a customer pastes: leading and trailing spaces, spaces inside, hyphens and other dashes, lower case. cleanOf removes the noise and raises the case, so "autumn-15" and " AUTUMN 15 " are the same code. The field says the same in its hint, and the typed value is shown as capitals.
How does the replace check work?
With a code on the order, a valid second code does not replace it silently. The block shows the question, the total you would pay if you keep the first and the total with the second, and two buttons; focus moves to Replace. Keep leaves the order as it was. Until you answer, a new Add is refused and says so, and Continue to payment asks you to answer first.
What reasons can a code be refused for?
Unknown, already applied, ended (a typed end date compared with the fixed AS_OF, never the visitor's clock) and under the minimum spend (min in cents, compared with the items). They live in one pure function, problemOf, so the same rules can run on your server.
Does anything move when a code is added, asked about or removed?
No. The chip, the question and the idle sentence sit in one grid cell, and the two not shown are invisible and inert, so the cell is always as tall as the question. The disclosure body animates its row height only when you open or close it.
How does it look on a phone?
The summary goes under the code area, and the foot is pinned to the bottom of the screen with a full-width Continue to payment. The two totals in the question sit in two columns that fit 320 pixels.
What stays local in the demo, and how do I wire it up?
Everything. The codes are a typed list and nothing is applied to a real order. In your app send the code to your API, show its reason in the same line when it refuses, and render the totals it returns. To go to your payment step: give the Shine Button render set to an a element with nativeButton={false}.