Introduction
In today’s global marketplace, offering prices in your customers’ local currency isn’t just a nicety—it’s an expectation. A smooth currency switcher can boost conversions, reduce abandoned carts, and enhance user trust. In this guide, we’ll walk through building a fully custom currency switcher from scratch using plain JavaScript, HTML, and CSS. You’ll learn how to fetch live exchange rates, handle edge cases gracefully, and optimize for performance and accessibility. By the end, you’ll have a reusable component you can drop into any project—no heavy libraries required.

Why You Need a Currency Switcher
- Global Reach: Let international visitors shop without manual conversions.
- Higher Conversions: Shoppers are more likely to buy when they see prices in their own currency.
- Improved UX: Eliminates confusion and builds trust.
Planning Your Switcher
Before writing code, outline your requirements:
- Supported currencies (e.g., USD, EUR, GBP, JPY)
- Source for live exchange rates
- Fallback when API fails
- Styling to match your design system
Defining the HTML Structure
htmlCopyEdit<div class="currency-switcher">
<label for="currency-select">Currency:</label>
<select id="currency-select">
<option value="USD" selected>USD</option>
<option value="EUR">EUR</option>
<option value="GBP">GBP</option>
<option value="JPY">JPY</option>
</select>
</div>
<div class="prices">
<p class="price" data-base-price="49.99">$49.99</p>
<p class="price" data-base-price="120.00">$120.00</p>
</div>
- Each
.price
element stores adata-base-price
attribute with your site’s base currency (e.g., USD). - The
<select>
lets the user pick a target currency.
Styling the Switcher
cssCopyEdit.currency-switcher {
margin-bottom: 1rem;
}
.currency-switcher select {
padding: 0.5rem;
border-radius: 4px;
}
.price {
font-size: 1.25rem;
margin: 0.5rem 0;
}
Adjust colors and fonts to fit your branding.
Fetching Live Exchange Rates
You’ll need an API—popular free options include ExchangeRate-API, Open Exchange Rates (free tier), or Fixer.io.

Using a Public API
jsCopyEditasync function fetchExchangeRates(base = 'USD') {
const response = await fetch(
`https://api.exchangerate-api.com/v4/latest/${base}`
);
if (!response.ok) throw new Error('Failed to fetch rates');
const data = await response.json();
return data.rates; // { EUR: 0.84, GBP: 0.75, ... }
}
Handling CORS and Errors
- CORS: Ensure your API supports browser requests or route via your server.
- Error Fallback: Cache last-known rates in
localStorage
to use when the network fails.
jsCopyEditfunction saveRatesToCache(rates) {
localStorage.setItem('exchangeRates', JSON.stringify({
timestamp: Date.now(),
rates
}));
}
function getRatesFromCache(maxAgeMinutes = 60) {
const cached = JSON.parse(localStorage.getItem('exchangeRates') || '{}');
if (
cached.rates &&
Date.now() - cached.timestamp < maxAgeMinutes * 60 * 1000
) {
return cached.rates;
}
return null;
}
JavaScript Implementation
Initializing the Switcher
jsCopyEditconst currencySelect = document.getElementById('currency-select');
const priceElements = document.querySelectorAll('.price');
let exchangeRates = getRatesFromCache() || {};
if (!exchangeRates || !exchangeRates.USD) {
fetchExchangeRates('USD')
.then(rates => {
exchangeRates = rates;
saveRatesToCache(rates);
})
.catch(err => console.error(err));
}
Updating Prices on Change
jsCopyEditcurrencySelect.addEventListener('change', () => {
const targetCurr = currencySelect.value;
if (!exchangeRates[targetCurr]) {
fetchExchangeRates('USD')
.then(rates => {
exchangeRates = rates;
saveRatesToCache(rates);
updateDisplayedPrices(targetCurr);
})
.catch(err => {
console.error(err);
updateDisplayedPrices(targetCurr, true);
});
} else {
updateDisplayedPrices(targetCurr);
}
});
function updateDisplayedPrices(curr, useFallback = false) {
priceElements.forEach(el => {
const base = parseFloat(el.dataset.basePrice);
const rate = useFallback
? getRatesFromCache()?.[curr] || 1
: exchangeRates[curr];
const converted = (base * rate).toFixed(2);
el.textContent = `${curr} ${converted}`;
});
}
Optimizations & Best Practices
- Debounce Rapid Changes: If your UI has sliders or rapid updates, debounce calls.
- Lazy-Load Rates: Only fetch rates when the switcher enters the viewport (Intersection Observer).
- Minimize API Calls: Respect rate limits by using caching and only re-fetching hourly or daily.

Accessibility & Testing
- ARIA Labels:
<select aria-label="Select currency"></select>
. - Keyboard Navigation: Ensure the switcher is focusable and users can tab through.
- Unit Tests: Mock
fetch
in Jest to verify price conversions. - Cross-Browser Checks: Test in Chrome, Firefox, Safari, and mobile browsers.
Real-World Analogy
Think of your currency switcher as a multilingual interpreter at a conference:
- You have speakers (base prices) in one language (USD).
- The interpreter (exchange API) converts their words into the attendee’s language (EUR, JPY).
- Caching is like having a printed cheat sheet of common phrases—faster and useful if the interpreter steps out for a moment.
Conclusion
Building a custom currency switcher with JavaScript empowers you to provide a seamless international shopping experience without relying on heavyweight libraries. By structuring your HTML cleanly, fetching and caching exchange rates smartly, and encapsulating your logic in maintainable functions, you create a component that’s both performant and user-friendly. Remember to optimize for accessibility, handle errors gracefully, and test across browsers. Now you’re ready to integrate global pricing into any web project—happy coding!