shadcn.io is not affiliated with official shadcn/ui
Apply Coupon Dialog Block for Solid
A cookware shop's basket with its promo-code dialog shown open: one field that judges five typed codes with five different answers, applied codes kept as removable chips, and a summary that is worked out again after every change.
Installation
Overview
Let shoppers add a discount without leaving the basket, with this promo-code dialog for Solid with Ark UI. It opens from the basket of Kettlecraft, an enamel cookware shop: the page's header holds an outline Add a promo code Button, and the basket itself, three drawn pieces and an order summary, sits under a soft scrim inside a hairline frame with a plus mark on each corner. The dialog is one Field with an Input that judges what you type against five codes with five different answers: one applies, one applies and stacks with it, one has expired, one was already used on the account, and one needs a bigger basket and says by exactly how much. Each failure is its own sentence under the field. A code that works stays on the order as a Badge chip with what it takes off and a button to take it back, and the summary under it, subtotal, discounts, shipping and total, is worked out again after every change; the basket behind the dialog prints the same totals. Apply code is the only Shine Button. For preview the dialog is drawn open in place when the page loads; the header button opens the real Dialog, with the same field and the same applied codes. For carts, checkouts, subscriptions and any order that takes a code.
FAQ
Why is the dialog already open, and what do I keep?
So you can see it without a click. On load the dialog is a copy drawn in place over the basket: a native dialog element that isn't modal, so nothing is trapped, hidden or focused, and the page around the preview keeps working. The Add a promo code button opens the real Dialog, modal, with its focus trap, Escape and focus return, and the copy stays closed until a reload. Both show the same CouponFields component and share the block's state, so a code typed in the copy is there in the real one and so are the codes already applied. In your app keep the Dialog and drop the copy: the "in-place" state, the scrim and the dialog element inside the frame.
Where do the five answers come from?
From CODES and the small function problemWith, both in the file. A code is unknown, already on the order, a second percentage code, expired (its last day is compared with the block's AS_OF), already used on the account, or short of its minimum spend, checked in that order, and the message names the code and says what to do, such as "Add $14.00 more to use HEAVY5." One percentage code per order is a rule of problemWith; free shipping stacks with it. In your app send the code with the cart to your server and show the reason it returns in the same slot; keep the sentences specific, since a bare "Invalid code" gives people nothing to fix.
How is the summary worked out?
By totalsOf, a pure function of the codes applied. Prices are whole cents, so nothing drifts: a percentage code takes its share of the subtotal rounded to a cent, a fixed code takes its amount, free shipping removes the shipping, and the total is the subtotal minus the discounts plus the shipping. The dialog and the basket behind it call the same function, so they never disagree, and each price is one left-to-right run inside a right-to-left page. Replace the cart constants with your own, and let your server stay the source of truth when it prices the order for real.
Does applying a code charge or use it up?
No. Here an applied code is only state in the block, and the polite status message says what it took off and what the total is now, ending with "Nothing was charged in this demo." Reload and the codes are gone. In your app apply the code on your server in apply, which should also reserve it against the order, and show its answer: the chip and the totals from the response, or the reason in the field's error line with the dialog still open.
Is it accessible from the keyboard?
Yes. The real dialog is named by its title and described by its sentence, focus starts in the code field and stays inside until it closes, and Escape, the close button and Cancel (Done once a code is applied) all return focus to the Add a promo code button. The trigger is the button wrapped in DialogTrigger with asChild, and the dialog is controlled with open and onOpenChange. Applying keeps the dialog open so you can stack a second code, with focus back in the field; each chip's remove button names its code, and one polite status message reports what changed. The copy shown on load moves no focus; Escape inside it and its buttons close it and put focus on the header button.
How does it look on a phone?
The copy shown on load docks to the foot of the page as a bottom sheet, its actions pinned at the bottom with Apply code on top, and the field, the chips and the summary scroll above them. The basket behind it drops its drawn order summary, since the dialog shows the totals. The real dialog keeps the Dialog's own phone layout, a sheet stuck to the bottom of the screen.