shadcn.io is not affiliated with official shadcn/ui
Multi-Currency Checkout Block for React
A currency step for an art-print shop: the order written in dollars, euros or yen from whole US cents at a dated typed rate, a native radio row for each saying what happens to the card, and a Pay button that follows the choice.
Installation
Overview
Let people see the price and pay in their own currency, with this multi-currency checkout for React with Base UI. A compact header from Evenwood, a shop for art prints, sits over a hairline frame with a plus mark on each corner. The first band is the order: two prints and flat-packed shipping, $84.00, written in whichever currency is chosen, each line converted from whole US cents and rounded on its own so the lines always add up to the total. The second band is a group of three native radio rows (US dollars, euros, Japanese yen), each with its own total and one honest sentence about the card: dollars are charged as dollars and your bank converts if the card is in another currency, euros may carry a bank fee on a card from outside the euro area, yen are whole yen with no decimals. A rate line under them gives the typed rate and its date ("1 USD = 0.92 EUR"), and says it is not a live one. The foot has a polite status line and the only Shine Button, labelled with the amount in the chosen currency. It's built from Shine Button and Guides, with a CSS entrance only. For art, print and goods shops that price in one currency and charge in a few.
FAQ
Where do the exchange rates come from?
They are typed at the top of the file in CURRENCIES, as minor units of the currency for every 10,000 US cents (9200 for euros, 15,000 for yen), and the rate line says the figure is typed and dated, not live. In your shop read them from your payment provider or a rates service, keep the date you read them, and show that date. The converted amounts you show should be the ones you charge, so quote the rate once and store it with the order.
How are the amounts rounded so they add up?
Each line is converted on its own from whole cents and rounded half up to the currency's smallest unit, and the total is the sum of the converted lines, never the converted total. That way a column of prices always adds up to the figure at the bottom. Yen has no decimals, so decimals is 0 and the conversion rounds to whole yen; the helper money writes each currency its own way (a comma as the decimal point and a trailing euro sign for euros).
Which currency is the card actually charged in?
The one you choose, and each row says what that means: charged in dollars, charged in euros, charged in whole yen. The block does not claim a bank will not add its own fee, because it cannot know; it says your bank may. If your payment provider can only settle in one currency, remove the other rows and keep the display-only conversion, with the sentence changed to say the card is charged in that currency.
How does the radio group work with a keyboard?
The three rows are native radio inputs sharing one name inside a fieldset with a hidden legend, so Tab enters the group, the arrow keys move and select, and the drawn dot and the shaded row follow the checked state. Choosing a row updates every amount, the Pay label and the status line at once, and nothing in the panel changes height because every row, the rate line and the status line reserve their space.
What stays local in the demo, and how do I wire it up?
Everything. The choice lives in the block's state and Pay only says that nothing was charged. In your app keep the chosen currency with the order, create the payment in that currency with your provider, format with Intl.NumberFormat after mount if you prefer the browser's rules to the typed ones, and show the provider's error in the same status line if the charge fails.