E-commerce & Retail

Free Currency Switcher Widget

Let visitors see your prices in their own currency with one dropdown.

A lightweight dropdown that live-converts any price on your page into the currency your visitor picks. Tag prices with a data attribute, supply your exchange rates as JSON, and the widget handles formatting and remembers the choice on return visits. Built for shops, SaaS pricing pages, and any site selling to an international audience.

Live demo

Embed this widget

Paste either snippet into your page — a Custom HTML block on WordPress, Shopify, or Squarespace works too. Both snippets update live as you customize, so what you copy is exactly what you configured.

Hosted (recommended)html
<script src="https://hayneslab.dev/widgets/currency-switcher.js" data-hl-config='{"EUR":0.92,"GBP":0.79,"JPY":149.5,"CAD":1.36,"AUD":1.52}' data-hl-currencies="USD,EUR,GBP,JPY,CAD,AUD" async></script>
<!-- Free Currency Switcher widget by haynes lab — https://hayneslab.dev/tools/widgets/currency-switcher -->
Inline (self-contained)html
<!-- Free Currency Switcher widget by haynes lab — https://hayneslab.dev/tools/widgets/currency-switcher -->
<script data-hl-config='{"EUR":0.92,"GBP":0.79,"JPY":149.5,"CAD":1.36,"AUD":1.52}' data-hl-currencies="USD,EUR,GBP,JPY,CAD,AUD">
/*!
 * haynes lab — Currency Switcher widget (free)
 * Docs & embed code: https://hayneslab.dev/tools/widgets/currency-switcher
 */
(function () {
  'use strict';

  var PREFIX = 'hlw-currency-switcher';
  var STYLE_ID = 'hlw-currency-switcher-styles';
  var STORAGE_KEY = 'hlw-currency-switcher-currency';

  function findScript() {
    return document.currentScript ||
      document.querySelector('script[src*="widgets/currency-switcher.js"]');
  }

  function injectStyles() {
    if (document.getElementById(STYLE_ID)) return;
    var css =
      '.' + PREFIX + ', .' + PREFIX + ' * { box-sizing: border-box; }' +
      '.' + PREFIX + ' { display: inline-block; font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;' +
      ' font-size: 14px; line-height: 1.4; color: #1f2937; }' +
      '.' + PREFIX + '__row { display: inline-flex; align-items: center; gap: 8px; padding: 6px 10px;' +
      ' background: #ffffff; border: 1px solid #e5e7eb; border-radius: 8px; }' +
      '.' + PREFIX + '__label { font-size: 13px; color: #6b7280; white-space: nowrap; }' +
      '.' + PREFIX + '__select { appearance: none; -webkit-appearance: none; font: inherit; color: #111827;' +
      ' background: transparent url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' width=\'10\' height=\'6\' viewBox=\'0 0 10 6\'%3E%3Cpath d=\'M1 1l4 4 4-4\' fill=\'none\' stroke=\'%236b7280\' stroke-width=\'1.5\' stroke-linecap=\'round\'/%3E%3C/svg%3E") no-repeat right 2px center;' +
      ' border: 0; padding: 2px 18px 2px 2px; cursor: pointer; font-weight: 600; }' +
      '.' + PREFIX + '__select:focus-visible { outline: 2px solid #f97316; outline-offset: 2px; border-radius: 4px; }' +
      '.' + PREFIX + '__credit { display: flex; justify-content: flex-end; margin-top: 4px; font-size: 11px;' +
      ' align-items: center; gap: 4px; color: #9ca3af; text-decoration: none; }' +
      '.' + PREFIX + '__credit svg { display: block; }' +
      '.' + PREFIX + '__credit:hover { color: #6b7280; text-decoration: underline; }';
    var style = document.createElement('style');
    style.id = STYLE_ID;
    style.textContent = css;
    document.head.appendChild(style);
  }

  function parseRates(script) {
    var raw = script.getAttribute('data-hl-config');
    if (!raw) {
      console.warn('[haynes lab currency-switcher] Missing required data-hl-config attribute (JSON object of exchange rates relative to the base currency, e.g. {"EUR":0.92}). Nothing rendered.');
      return null;
    }
    var rates;
    try {
      rates = JSON.parse(raw);
    } catch (e) {
      console.warn('[haynes lab currency-switcher] data-hl-config is not valid JSON. Nothing rendered.', e);
      return null;
    }
    if (!rates || typeof rates !== 'object' || Array.isArray(rates)) {
      console.warn('[haynes lab currency-switcher] data-hl-config must be a JSON object of {"CODE": rate}. Nothing rendered.');
      return null;
    }
    return rates;
  }

  function el(tag, className, text) {
    var node = document.createElement(tag);
    if (className) node.className = className;
    if (text != null) node.textContent = text;
    return node;
  }

  function logoSvg() {
    var svgNS = 'http://www.w3.org/2000/svg';
    var svg = document.createElementNS(svgNS, 'svg');
    svg.setAttribute('viewBox', '0 0 32 32');
    svg.setAttribute('width', '13');
    svg.setAttribute('height', '13');
    svg.setAttribute('aria-hidden', 'true');
    svg.setAttribute('focusable', 'false');
    function circle(cx, cy, r, attrs) {
      var c = document.createElementNS(svgNS, 'circle');
      c.setAttribute('cx', cx);
      c.setAttribute('cy', cy);
      c.setAttribute('r', r);
      for (var key in attrs) c.setAttribute(key, attrs[key]);
      svg.appendChild(c);
    }
    circle(16, 16, 16, { fill: '#0f0a0a' });
    circle(16, 16, 12, { fill: 'none', stroke: '#fff5f5', 'stroke-width': 1, opacity: 0.3 });
    circle(16, 16, 9, { fill: 'none', stroke: '#fff5f5', 'stroke-width': 1, opacity: 0.5 });
    circle(16, 16, 6, { fill: 'none', stroke: '#fff5f5', 'stroke-width': 1.2, opacity: 0.9 });
    circle(16, 16, 3, { fill: '#06b6d4' });
    circle(24.5, 7.5, 2, { fill: '#f97316' });
    circle(7, 16, 1.8, { fill: '#f97316' });
    circle(20, 20, 1.5, { fill: '#f97316' });
    return svg;
  }

  function formatPrice(amount, currency) {
    try {
      return new Intl.NumberFormat(undefined, { style: 'currency', currency: currency }).format(amount);
    } catch (e) {
      return currency + ' ' + amount.toFixed(2);
    }
  }

  function build(script) {
    var rates = parseRates(script);
    if (!rates) return;

    var base = (script.getAttribute('data-hl-base') || 'USD').toUpperCase();
    rates[base] = 1;

    var currencies = (script.getAttribute('data-hl-currencies') || base)
      .split(',')
      .map(function (c) { return c.trim().toUpperCase(); })
      .filter(function (c, i, arr) { return c && arr.indexOf(c) === i; });
    if (!currencies.length) currencies = [base];

    currencies.forEach(function (code) {
      var rate = parseFloat(rates[code]);
      if (isNaN(rate) || rate <= 0) {
        console.warn('[haynes lab currency-switcher] No valid rate for "' + code + '" in data-hl-config; it will show base amounts.');
        rates[code] = 1;
      } else {
        rates[code] = rate;
      }
    });

    injectStyles();

    var root = el('div', PREFIX);
    var row = el('div', PREFIX + '__row');

    var selectId = PREFIX + '-select-' + Math.random().toString(36).slice(2, 8);
    var label = el('label', PREFIX + '__label', 'Currency');
    label.setAttribute('for', selectId);

    var select = el('select', PREFIX + '__select');
    select.id = selectId;
    select.setAttribute('aria-label', 'Display prices in');
    currencies.forEach(function (code) {
      var opt = el('option', null, code);
      opt.value = code;
      select.appendChild(opt);
    });

    row.appendChild(label);
    row.appendChild(select);
    root.appendChild(row);

    function apply(code) {
      var rate = rates[code];
      var targets = document.querySelectorAll('[data-hl-price]');
      Array.prototype.forEach.call(targets, function (node) {
        var amount = parseFloat(node.getAttribute('data-hl-price'));
        if (isNaN(amount)) return;
        node.textContent = formatPrice(amount * rate, code);
      });
      document.dispatchEvent(new CustomEvent('hl:currency-change', { detail: { currency: code, rate: rate } }));
    }

    select.addEventListener('change', function () {
      apply(select.value);
      try { localStorage.setItem(STORAGE_KEY, select.value); } catch (e) { /* private mode */ }
    });

    var saved = null;
    try { saved = localStorage.getItem(STORAGE_KEY); } catch (e) { /* private mode */ }
    if (saved && currencies.indexOf(saved) !== -1) {
      select.value = saved;
      apply(saved);
    }

    var credit = el('a', PREFIX + '__credit');
    credit.appendChild(logoSvg());
    credit.appendChild(document.createTextNode('Powered by haynes lab'));
    credit.href = 'https://hayneslab.dev/tools/widgets/currency-switcher';
    credit.target = '_blank';
    credit.rel = 'noopener';
    root.appendChild(credit);

    if (script.parentNode && script.parentNode.nodeName !== 'HEAD') {
      script.insertAdjacentElement('afterend', root);
    } else {
      document.body.appendChild(root);
    }
  }

  function init() {
    var script = findScript();
    if (!script) {
      console.warn('[haynes lab currency-switcher] Could not locate its own script tag. Nothing rendered.');
      return;
    }
    build(script);
  }

  if (document.body) {
    init();
  } else {
    document.addEventListener('DOMContentLoaded', init);
  }
})();

</script>

Configuration

Every option is an attribute on the script tag — the same attributes the customizer above exposes. All attributes are optional unless marked required — defaults apply when omitted.

AttributeDefaultDescription
data-hl-config(required)JSON object mapping currency codes to their rate relative to the base currency, e.g. {"EUR":0.92,"GBP":0.79}.
data-hl-base"USD"ISO code of the currency your data-hl-price amounts are written in.
data-hl-currencies"USD"Comma-separated list of currency codes offered in the dropdown.

Frequently asked questions

How do I install the Currency Switcher?

Paste the script tag where you want the dropdown to appear, then add a data-hl-price attribute (the amount in your base currency) to any element holding a price, e.g. <span data-hl-price="49">$49.00</span>. The dropdown renders inline right where the script tag sits.

How do I set the currencies and exchange rates?

Use data-hl-base for your store's currency, data-hl-currencies for the comma-separated codes in the dropdown, and data-hl-config for a JSON object of rates relative to the base (1 USD = rate). You update rates by editing the JSON whenever you like.

Does it work on WordPress, Shopify, or Squarespace?

Yes — it is a single vanilla JS file with no dependencies. Paste the snippet into any HTML block, theme file, or custom code section and it works anywhere scripts are allowed.

More free widgets

Need a custom widget?

We build custom widgets, integrations, and whole products. Tell us what your site needs and we'll scope it.

Get in Touch