webdevpuneet.com

Pricing Currency Switcher — Free HTML CSS JS Snippet

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

CategoryPricing

Copy & paste

Source Code

<div class="pricing-page">
  <div class="pricing-header">
    <h2>Simple, transparent pricing</h2>
    <div class="currency-switch" id="currency-switch" role="group" aria-label="Select display currency">
      <button class="currency-opt active" data-currency="USD">USD</button>
      <button class="currency-opt" data-currency="EUR">EUR</button>
      <button class="currency-opt" data-currency="GBP">GBP</button>
      <button class="currency-opt" data-currency="INR">INR</button>
    </div>
  </div>

  <div class="pricing-grid">
    <div class="plan-card" data-base-price="9">
      <p class="plan-name">Starter</p>
      <p class="plan-price"><span class="price-amount">$9</span><span class="price-period">/mo</span></p>
      <p class="plan-blurb">For individuals getting started</p>
      <ul class="plan-features">
        <li>1 project</li>
        <li>5GB storage</li>
        <li>Community support</li>
      </ul>
      <button class="plan-cta">Choose Starter</button>
    </div>

    <div class="plan-card featured" data-base-price="29">
      <span class="popular-tag">Most popular</span>
      <p class="plan-name">Growth</p>
      <p class="plan-price"><span class="price-amount">$29</span><span class="price-period">/mo</span></p>
      <p class="plan-blurb">For growing teams shipping fast</p>
      <ul class="plan-features">
        <li>Unlimited projects</li>
        <li>100GB storage</li>
        <li>Priority support</li>
        <li>Team roles &amp; permissions</li>
      </ul>
      <button class="plan-cta primary">Choose Growth</button>
    </div>

    <div class="plan-card" data-base-price="79">
      <p class="plan-name">Scale</p>
      <p class="plan-price"><span class="price-amount">$79</span><span class="price-period">/mo</span></p>
      <p class="plan-blurb">For larger orgs with custom needs</p>
      <ul class="plan-features">
        <li>Unlimited everything</li>
        <li>1TB storage</li>
        <li>Dedicated support</li>
        <li>SSO &amp; audit logs</li>
      </ul>
      <button class="plan-cta">Choose Scale</button>
    </div>
  </div>

  <p class="billing-disclosure" id="billing-disclosure">Prices shown in USD, billed in USD.</p>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

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.

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 Matrix Table gives international visitors a fully localized-feeling evaluation experience even when the backend billing remains single-currency.
Related: Dense Feature Comparison Matrix
See the Dense Feature Comparison Matrix for a related pricing pattern worth pairing with this one.

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.