shadcn.io is not affiliated with official shadcn/ui
Add to Cart Dialog Block for Ark UI
A stool shop's product page with its add-to-cart dialog shown open: two heights that redraw the stool and move the price, three finishes with a sentence each, a quantity that stops at the stock left, and a total that follows the choices.
Installation
Overview
Let a shopper settle the details before the cart, with this add-to-cart dialog for React with Ark UI. It opens from the Linnet stool's page at Stoolwright, a workshop that turns oak stools by hand: the header holds an outline Add to cart Button, and the shop itself is drawn under a soft scrim inside a hairline frame with a plus mark on each corner, a cart chip at its top end. The dialog is a split. At the start the stools are drawn at their two heights with the chosen one in full strength. At the end a Toggle Group picks Counter or Bar, a Radio Group picks Oiled, Waxed or Raw with a sentence and a price change under each, and a Number Input sets the quantity, which stops politely at what is in stock. The total beside it is quantity times unit price and moves with every choice. Add to cart, the only Shine Button, raises the cart chip's count and closes. 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 the same choices. For shops, quick-view modals and any product with variants and limited stock.
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 shop: 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 to cart button in the header 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 CartFields component and share the block's state, so a height picked 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.
How do the choices change the price?
HEIGHTS holds each height's price and stock, and FINISHES holds what each finish adds or takes off, so the unit price is the height's price plus the finish's change, and the total is the quantity times that. The stool at the start of the dialog redraws from the same height, and the finish rows show their change in dollars next to their name, so the number never surprises. Prices are whole dollars formatted with Intl.NumberFormat; swap in the currency and cents your shop uses.
What happens when someone asks for more than is in stock?
The quantity is kept between 1 and the stock of the chosen height, and the line under the field says so in words ("Only 2 in stock. That is the most you can add.") whether the number was typed or stepped to the limit. The total always uses the kept number, and switching to a height with less stock pulls the quantity down with it. The line is a hint linked to the field, two lines high, so it never moves the total. In your app read the stock when the dialog opens and say if it changed before the order is placed.
What does Add to cart do, and how do I wire it up?
Here it adds the quantity to the cart chip's count in local state, closes the dialog, and says in a polite status message what was added and that nothing was ordered in this demo. No request goes out and nothing is stored. In your app send the height, finish and quantity to your cart in add, update the cart from the response, and if the server refuses because the stock changed, show its reason in the stock line and keep the dialog open.
Is it accessible from the keyboard?
Yes. The real dialog is named by its title and described by its sentence, focus starts on the height choices and stays inside until it closes, and Escape, Cancel and the close button all return focus to Add to cart. The height is a labelled group of two toggles, the finish a labelled radio group read as one question with a sentence per option, and the quantity a labelled number field with the stock line as its description. The trigger is the button wrapped in DialogTrigger with asChild, and the dialog is controlled with open and onOpenChange. The copy shown on load moves no focus; Escape inside it, Cancel and its close button close it and put focus on Add to cart.
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 Add to cart on top, and the fields scroll above them. The stools shrink to a short strip over the fields instead of a column beside them. The real dialog keeps the Dialog's own phone layout: a sheet stuck to the bottom of the screen.