Pricing Currency Switcher — Free HTML CSS JS Snippet

Pricing Card Currency Switcher · Pricing · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Single USD source of truth per card via data-base-price, avoiding compounding rounding error on re-conversion
Intl.NumberFormat handles symbol, placement, grouping, and decimals correctly per currency and locale
Event delegation on #currency-switch — one listener handles all four currency buttons
All pricing cards update together in one pass, keeping the table visually consistent mid-switch
Brief opacity fade on price text gives visible confirmation the number actually changed
Dynamic billing-disclosure text clarifies real charge currency whenever display currency differs from USD
Segmented control with .active state styling mirrors standard iOS/Material segmented-control conventions
Rate table and locale mapping centralized in one RATES object for easy extension to more currencies

About this UI Snippet

Pricing Card Currency Switcher — Live Multi-Currency Display, Intl.NumberFormat & Billing Transparency

Screenshot of the Pricing Card Currency Switcher snippet rendered live

International SaaS pricing pages routinely show prices in a visitor's local currency to reduce the mental friction of converting an unfamiliar figure before deciding to buy — a shopper in Mumbai reads ₹749 far faster than $9.00. This snippet builds a working currency switcher for a three-tier pricing table: a segmented control lets the user pick USD, EUR, GBP, or INR, and every card's displayed price updates simultaneously using a small hardcoded exchange-rate table and locale-correct currency formatting.

Storing one source of truth per plan

Each .plan-card element carries its true price as a data-base-price attribute in US dollars (e.g. data-base-price="29"), which is the single source of truth the switcher always converts *from*, regardless of which currency is currently displayed. This matters because repeatedly converting a previously-converted price (EUR back to GBP, for instance) compounds rounding error — always recomputing from the original USD base avoids that entirely.

A small rate table and Intl.NumberFormat

The RATES object maps each supported currency code to an illustrative conversion rate and an appropriate locale string (en-US, de-DE, en-GB, en-IN). formatPrice() multiplies the USD base by the target rate, rounds to a whole number for clean display, and hands the result to Intl.NumberFormat(locale, { style: 'currency', currency, maximumFractionDigits: 0 }). This single built-in browser API — no library, no manual symbol lookup table — correctly handles everything locale-specific about currency display: the right symbol ($, , £, ), its position relative to the digits (prefix in most locales, but placement and spacing rules genuinely differ), thousands-grouping conventions, and decimal separators. Hardcoding a symbol-and-concatenate approach ('$' + amount) would get every one of those details wrong for at least one of the four supported currencies.

Updating every card at once

Clicking a currency option calls applyCurrency(currency), which iterates every .plan-card, reads its base price, and rewrites the .price-amount text via formatPrice() — all three cards update together in a single pass, so the pricing table never shows a mix of currencies mid-transition. A brief opacity fade (amountEl.style.opacity) on each price during the swap gives a small moment of visual feedback that the numbers actually changed, rather than an instant, easy-to-miss text swap.

The billing disclosure — a small detail with outsized trust impact

Most SaaS billing systems still charge the customer's card in a single base currency (commonly USD) even when the marketing site *displays* prices in the visitor's local currency for convenience — the local-currency figure is an estimate, and the actual charge amount and any bank-side conversion fee depend on the card issuer's own exchange rate at the time of the transaction. Silently showing a EUR price with no clarification about what currency will actually be charged is a subtle trust violation that surfaces painfully at the moment a customer sees an unexpected amount on their statement. This snippet surfaces that reality directly: the disclosure line beneath the pricing grid reads "Prices shown in EUR, billed in USD. Your bank may apply its own conversion rate." whenever a non-USD currency is selected — a small piece of copy that meaningfully improves billing transparency and reduces support tickets and chargebacks from surprised customers.

Why this matters for 2026 trust-driven UX

Transparent, honest pricing disclosure is a core pillar of trust-driven UX: showing a number without the context of what actually gets charged is a dark pattern by omission, even if unintentional. Pairing a genuinely useful convenience feature (localized price display) with an equally visible disclosure about the underlying billing currency is the correct way to implement this pattern — convenience without hidden surprises.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Paste this snippet into an AI coding assistant like Claude and ask it to explain exactly why formatPrice() always converts from the original data-base-price USD value rather than the currently displayed price, and how Intl.NumberFormat's locale argument changes the rendered output for the same numeric amount across en-US, de-DE, en-GB, and en-IN. It's also a strong candidate to extend with AI help: ask it to fetch live exchange rates from a currency API with periodic caching instead of the hardcoded RATES table, add geolocation-based auto-detection of the visitor's likely currency on first load, or add an annual/monthly billing toggle that combines with the currency switcher so both dimensions update the displayed price together correctly.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a pricing table with a live currency switcher in plain HTML, CSS, and JavaScript, using no libraries — just the built-in Intl.NumberFormat API for currency formatting.

Requirements:
- Display 2-3 pricing tier cards, each with its true price stored in US dollars as a single source of truth (e.g. a data attribute), never in a display-only converted form.
- A segmented control (or dropdown) offering at least four currencies (e.g. USD, EUR, GBP, INR) where exactly one option is visually marked active at a time.
- Selecting a currency must update every pricing card's displayed price simultaneously, using a small hardcoded exchange-rate table mapping each supported currency to a conversion rate and the correct Intl locale string for formatting.
- All currency conversions must always compute from each card's original USD base price, never from whatever is currently displayed, to avoid compounding rounding error across repeated switches.
- Format every displayed price using Intl.NumberFormat with style "currency" and the correct currency code, so symbol placement, grouping, and decimals are locale-correct automatically rather than manually concatenated.
- Include a visible disclosure line beneath the pricing table stating that prices are shown in the selected currency but billed in the base currency (USD), and update this disclosure's exact wording whenever the selected currency changes, since this is a real billing-transparency requirement and not just decorative text.
- Give some lightweight visual feedback (such as a brief fade) when prices update, so it's clear to the user that the switch actually took effect rather than being an instant, easy-to-miss text change.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Click a currency option to switchThe #currency-switch segmented control listens for clicks via event delegation. Clicking any .currency-opt button calls applyCurrency(currency), which updates the .active class and re-renders every card's price.
  2. 2
    Understand the base-price data attributeEach .plan-card stores its true USD price as data-base-price (e.g. data-base-price="29"). formatPrice() always converts from this original USD figure, never from a previously displayed currency, to avoid compounding rounding error.
  3. 3
    See the live Intl.NumberFormat conversionformatPrice(baseUsd, currency) multiplies the base by RATES[currency].rate and formats the result with new Intl.NumberFormat(locale, { style: "currency", currency }) — producing correctly symbol-placed, locale-formatted output like €26 or ₹2,409 with zero manual string concatenation.
  4. 4
    Read the billing disclosure updateSwitching away from USD updates #billing-disclosure to explicitly state prices are billed in USD regardless of display currency, via the ternary inside applyCurrency(). This is a required transparency detail, not just a stylistic footnote.
  5. 5
    Add a new currencyAdd an entry to the RATES object with a rate and matching Intl locale string, e.g. JPY: { rate: 147.2, locale: "ja-JP" }, and add a matching <button class="currency-opt" data-currency="JPY">JPY</button> inside #currency-switch.
  6. 6
    Connect to a live exchange-rate APIReplace the hardcoded RATES object with rates fetched from a currency API (refreshed periodically and cached, e.g. hourly) so displayed conversions stay accurate rather than drifting from real market rates over time.

Real-world uses

Common Use Cases

International SaaS and subscription pricing pages
Software products selling globally show prices in a visitor's likely local currency to reduce the cognitive load of mental currency conversion during evaluation, which measurably improves pricing-page conversion for international traffic while keeping actual billing simple by charging one base currency behind the scenes.
E-commerce storefronts with region-aware price display
Online stores serving multiple countries often default the currency switcher to the visitor's detected region (via IP geolocation or Accept-Language headers) while still processing payment in the merchant's settlement currency, making this same base-price-plus-disclosure pattern directly applicable beyond SaaS pricing tables.
Billing transparency and reduced chargeback disputes
Clearly disclosing that displayed local-currency prices are estimates and the actual charge occurs in a different base currency reduces "surprise charge" disputes and support tickets, since customers see the disclosure before committing rather than discovering the discrepancy on their bank statement afterward.
Teaching Intl.NumberFormat for currency-correct UI text
This snippet is a clean, minimal reference for using the built-in Intl.NumberFormat API to render currency values correctly across locales, avoiding the common anti-pattern of hardcoding a symbol-and-concatenate approach that breaks for currencies with different symbol placement or grouping rules.
A/B testing localized pricing presentation
Product and growth teams can use a currency switcher like this as the display layer for experiments testing whether localized price presentation improves conversion in specific markets, without needing to change the underlying billing system's currency at all.
Multi-region marketing sites alongside plan comparison tables
Pricing pages frequently sit next to feature-comparison tables and FAQs; pairing this switcher with a Feature Comparison Table gives international visitors a fully localized-feeling evaluation experience even when the backend billing remains single-currency.

Got questions?

Frequently Asked Questions

Most SaaS billing providers (Stripe, Paddle, and similar) settle merchant payouts in a single base currency, and many merchants choose to charge customers in that same base currency to avoid holding multi-currency balances and reconciling exchange-rate exposure. The local-currency price shown on the page is a convenience estimate to help the visitor understand roughly what they'll pay in familiar terms — the disclosure line makes this explicit so there's no surprise on the customer's actual statement.

Currency formatting rules genuinely differ by locale and currency: symbol placement (before vs. after the number), spacing, thousands-grouping characters (comma vs. period vs. space), and decimal conventions are not the same across USD, EUR, GBP, and INR. Intl.NumberFormat(locale, { style: 'currency', currency }) is a built-in, zero-dependency browser API that handles all of this correctly per the CLDR (Unicode Common Locale Data Repository) standard, which a manual string-concatenation approach would get wrong for at least one supported currency.

This snippet keeps the rate table static and clearly commented as illustrative to keep the demo self-contained with no network dependency. In production, hardcoded rates will drift from real market rates over time and should be replaced with rates fetched periodically from a currency-exchange API (refreshed hourly or daily is typical for a marketing pricing page, since sub-minute freshness isn't necessary for display-only estimates) and cached to avoid rate-limiting a live API on every page load.

Always convert from a single, unchanging source-of-truth value — this snippet stores the true USD price in each card's data-base-price attribute and recomputes formatPrice(baseUsd, currency) fresh from that original value on every switch, rather than converting the currently-displayed number again. Converting an already-converted, already-rounded number repeatedly (USD to EUR, then that EUR figure to GBP) compounds rounding error and can visibly drift from the correct value after a few switches.

Many production pricing pages default to a geolocation-based guess (via IP lookup or the Accept-Language header) rather than always defaulting to USD, since most visitors never manually change the currency switcher even when a more relevant option is available. If you add this, still leave the switcher visible and interactive — some visitors travel, use VPNs, or simply prefer a different display currency than their detected region.