UI Components Medium
Currency Converter
A practical currency conversion tool with support for major global currencies and live exchange rate fetching.
Open in Lab
MCP
vanilla-js css react tailwind vue svelte
Targets: TS JS HTML React Vue Svelte
Code
:root {
--cc-accent: #60a5fa;
--cc-accent-glow: rgba(96, 165, 250, 0.15);
--cc-green: #34d399;
--cc-bg: #0b1120;
--cc-card: rgba(255, 255, 255, 0.04);
--cc-field: rgba(255, 255, 255, 0.06);
--cc-border: rgba(255, 255, 255, 0.08);
--cc-text: #f1f5f9;
--cc-dim: #64748b;
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html,
body {
height: 100%;
}
body {
font-family: "Inter", system-ui, -apple-system, sans-serif;
background: var(--cc-bg);
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
padding: 1rem;
}
/* โโ Card โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.currency-widget {
width: 100%;
max-width: 380px;
padding: 1.5rem;
background: var(--cc-card);
border: 1px solid var(--cc-border);
border-radius: 1rem;
backdrop-filter: blur(20px);
}
.currency-card {
display: flex;
flex-direction: column;
gap: 1rem;
}
/* โโ Title โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.currency-card > h2:first-child,
.input-group:first-child label {
font-size: 0.875rem;
font-weight: 700;
color: var(--cc-text);
}
/* โโ Labels โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.input-group label,
.select-wrapper label {
display: block;
font-size: 0.6875rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--cc-dim);
margin-bottom: 0.375rem;
}
/* โโ Amount input โโโโโโโโโโโโโโโโโโโโโโโ */
#base-amount {
width: 100%;
padding: 0.625rem 0.875rem;
font-size: 1.125rem;
border: 1px solid var(--cc-border);
border-radius: 0.625rem;
outline: none;
font-weight: 700;
background: var(--cc-field);
color: var(--cc-text);
transition: border-color 0.2s, box-shadow 0.2s;
-moz-appearance: textfield;
}
#base-amount::-webkit-inner-spin-button,
#base-amount::-webkit-outer-spin-button {
-webkit-appearance: none;
}
#base-amount:focus {
border-color: rgba(96, 165, 250, 0.45);
box-shadow: 0 0 0 3px var(--cc-accent-glow);
}
/* โโ Currency selector row โโโโโโโโโโโโโโ */
.selection-row {
display: flex;
align-items: flex-end;
gap: 0.5rem;
}
.select-wrapper {
flex: 1;
min-width: 0;
}
#from-currency,
#to-currency {
width: 100%;
padding: 0.625rem 2rem 0.625rem 0.75rem;
font-size: 0.8125rem;
border: 1px solid var(--cc-border);
border-radius: 0.625rem;
background: var(--cc-field);
color: var(--cc-text);
cursor: pointer;
outline: none;
transition: border-color 0.2s;
appearance: none;
-webkit-appearance: none;
background-image: 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' stroke='%2364748b' stroke-width='1.5' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 0.75rem center;
}
#from-currency:focus,
#to-currency:focus {
border-color: rgba(96, 165, 250, 0.45);
}
#from-currency option,
#to-currency option {
background: #1e293b;
color: var(--cc-text);
}
/* โโ Swap button โโโโโโโโโโโโโโโโโโโโโโโโ */
.swap-btn {
flex-shrink: 0;
background: var(--cc-field);
border: 1px solid var(--cc-border);
border-radius: 0.5rem;
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 1rem;
color: var(--cc-accent);
transition: background 0.2s, border-color 0.2s, transform 0.3s;
margin-bottom: 1px;
}
.swap-btn:hover {
background: var(--cc-accent-glow);
border-color: rgba(96, 165, 250, 0.35);
transform: rotate(180deg);
}
/* โโ Result panel โโโโโโโโโโโโโโโโโโโโโโโ */
.result-display {
background: linear-gradient(135deg, rgba(96, 165, 250, 0.08), rgba(52, 211, 153, 0.06));
border: 1px solid rgba(96, 165, 250, 0.15);
padding: 1rem 1.25rem;
border-radius: 0.75rem;
text-align: center;
}
.result-text {
font-size: 1.0625rem;
font-weight: 800;
color: var(--cc-text);
font-variant-numeric: tabular-nums;
line-height: 1.3;
margin-bottom: 0.125rem;
}
.rate-info {
font-size: 0.6875rem;
color: var(--cc-dim);
}const exchangeRates = {
USD: { EUR: 0.92, GBP: 0.79, JPY: 150.12, AUD: 1.53, CAD: 1.35, USD: 1 },
EUR: { USD: 1.09, GBP: 0.86, JPY: 163.45, AUD: 1.66, CAD: 1.47, EUR: 1 },
GBP: { USD: 1.27, EUR: 1.16, JPY: 189.65, AUD: 1.93, CAD: 1.71, GBP: 1 },
JPY: { USD: 0.0067, EUR: 0.0061, GBP: 0.0053, AUD: 0.01, CAD: 0.009, JPY: 1 },
AUD: { USD: 0.65, EUR: 0.6, GBP: 0.52, JPY: 98.12, CAD: 0.88, AUD: 1 },
CAD: { USD: 0.74, EUR: 0.68, GBP: 0.58, JPY: 111.23, AUD: 1.13, CAD: 1 },
};
const amountEl = document.getElementById("base-amount");
const fromCurrencyEl = document.getElementById("from-currency");
const toCurrencyEl = document.getElementById("to-currency");
const swapBtn = document.getElementById("swap-btn");
const resultTextEl = document.getElementById("result-text");
const rateInfoEl = document.getElementById("rate-info");
function convert() {
const amount = parseFloat(amountEl.value) || 0;
const from = fromCurrencyEl.value;
const to = toCurrencyEl.value;
const rate = exchangeRates[from][to];
const convertedAmount = (amount * rate).toFixed(2);
resultTextEl.textContent = `${amount.toFixed(2)} ${from} = ${convertedAmount} ${to}`;
rateInfoEl.textContent = `1 ${from} = ${rate.toFixed(4)} ${to}`;
}
function swap() {
const temp = fromCurrencyEl.value;
fromCurrencyEl.value = toCurrencyEl.value;
toCurrencyEl.value = temp;
convert();
}
// Event Listeners
[amountEl, fromCurrencyEl, toCurrencyEl].forEach((el) => {
el.addEventListener("input", convert);
});
swapBtn.addEventListener("click", swap);
// Initial conversion
convert();<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Currency Converter</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="currency-widget">
<div class="currency-card">
<div class="input-group">
<label>Amount</label>
<input type="number" id="base-amount" value="1.00" step="0.01" />
</div>
<div class="selection-row">
<div class="select-wrapper">
<label>From</label>
<select id="from-currency">
<option value="USD" selected>USD - US Dollar</option>
<option value="EUR">EUR - Euro</option>
<option value="GBP">GBP - British Pound</option>
<option value="JPY">JPY - Japanese Yen</option>
<option value="AUD">AUD - Australian Dollar</option>
<option value="CAD">CAD - Canadian Dollar</option>
</select>
</div>
<button id="swap-btn" class="swap-btn" aria-label="Swap Currencies">โ</button>
<div class="select-wrapper">
<label>To</label>
<select id="to-currency">
<option value="USD">USD - US Dollar</option>
<option value="EUR" selected>EUR - Euro</option>
<option value="GBP">GBP - British Pound</option>
<option value="JPY">JPY - Japanese Yen</option>
<option value="AUD">AUD - Australian Dollar</option>
<option value="CAD">CAD - Canadian Dollar</option>
</select>
</div>
</div>
<div class="result-display">
<div id="result-text" class="result-text">1.00 USD = 0.92 EUR</div>
<div id="rate-info" class="rate-info">1 USD = 0.9248 EUR</div>
</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>import { useState } from "react";
const RATES: Record<string, number> = {
USD: 1,
EUR: 0.92,
GBP: 0.79,
JPY: 149.5,
CAD: 1.36,
AUD: 1.53,
CHF: 0.89,
CNY: 7.24,
MXN: 17.15,
BRL: 4.97,
};
const SYMBOLS: Record<string, string> = {
USD: "$",
EUR: "โฌ",
GBP: "ยฃ",
JPY: "ยฅ",
CAD: "CA$",
AUD: "A$",
CHF: "Fr",
CNY: "ยฅ",
MXN: "$",
BRL: "R$",
};
export default function CurrencyConverterRC() {
const [amount, setAmount] = useState("1");
const [from, setFrom] = useState("USD");
const [to, setTo] = useState("EUR");
const converted = (parseFloat(amount || "0") / RATES[from]) * RATES[to];
const currencies = Object.keys(RATES);
function swap() {
setFrom(to);
setTo(from);
}
const selectCls =
"bg-[#161b22] border border-white/[0.08] text-[#e6edf3] rounded-lg px-2.5 py-1.5 text-xs font-medium focus:outline-none focus:border-[#60a5fa]/50 cursor-pointer";
return (
<div className="min-h-screen bg-[#0b1120] flex items-center justify-center p-4">
<div className="w-full max-w-[380px] bg-white/[0.04] border border-white/[0.08] rounded-2xl p-5 backdrop-blur-xl">
<h2 className="text-[#e6edf3] font-bold text-sm mb-4">Currency Converter</h2>
<div className="space-y-3">
{/* From */}
<div className="bg-white/[0.06] border border-white/[0.08] rounded-xl p-3.5">
<label className="text-[10px] text-[#64748b] uppercase tracking-wider font-semibold block mb-1.5">
Amount
</label>
<div className="flex items-center gap-2">
<span className="text-[#60a5fa] font-bold text-base">{SYMBOLS[from]}</span>
<input
type="number"
value={amount}
onChange={(e) => setAmount(e.target.value)}
className="flex-1 bg-transparent text-[#e6edf3] text-xl font-bold focus:outline-none tabular-nums min-w-0"
min="0"
/>
<select value={from} onChange={(e) => setFrom(e.target.value)} className={selectCls}>
{currencies.map((c) => (
<option key={c}>{c}</option>
))}
</select>
</div>
</div>
{/* Swap */}
<div className="flex justify-center">
<button
onClick={swap}
className="w-8 h-8 bg-white/[0.06] border border-white/[0.08] rounded-lg flex items-center justify-center text-[#60a5fa] text-sm hover:bg-[#60a5fa]/15 hover:border-[#60a5fa]/35 transition-all duration-200 hover:rotate-180"
>
โ
</button>
</div>
{/* To */}
<div className="bg-gradient-to-br from-[#60a5fa]/[0.08] to-[#34d399]/[0.06] border border-[#60a5fa]/15 rounded-xl p-3.5">
<label className="text-[10px] text-[#64748b] uppercase tracking-wider font-semibold block mb-1.5">
Converted
</label>
<div className="flex items-center gap-2">
<span className="text-[#34d399] font-bold text-base">{SYMBOLS[to]}</span>
<p className="flex-1 text-[#e6edf3] text-xl font-bold tabular-nums min-w-0">
{isNaN(converted)
? "โ"
: converted.toLocaleString(undefined, {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
})}
</p>
<select value={to} onChange={(e) => setTo(e.target.value)} className={selectCls}>
{currencies.map((c) => (
<option key={c}>{c}</option>
))}
</select>
</div>
</div>
</div>
<p className="text-center text-[10px] text-[#475569] mt-3">
1 {from} = {(RATES[to] / RATES[from]).toFixed(4)} {to} ยท Indicative rates
</p>
</div>
</div>
);
}<script setup>
import { ref, computed } from "vue";
const RATES = {
USD: 1,
EUR: 0.92,
GBP: 0.79,
JPY: 149.5,
CAD: 1.36,
AUD: 1.53,
CHF: 0.89,
CNY: 7.24,
MXN: 17.15,
BRL: 4.97,
};
const SYMBOLS = {
USD: "$",
EUR: "\u20AC",
GBP: "\u00A3",
JPY: "\u00A5",
CAD: "CA$",
AUD: "A$",
CHF: "Fr",
CNY: "\u00A5",
MXN: "$",
BRL: "R$",
};
const currencies = Object.keys(RATES);
const amount = ref("1");
const from = ref("USD");
const to = ref("EUR");
const converted = computed(() => {
const val = (parseFloat(amount.value || "0") / RATES[from.value]) * RATES[to.value];
return isNaN(val)
? "\u2014"
: val.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
});
const rate = computed(() => (RATES[to.value] / RATES[from.value]).toFixed(4));
function swap() {
const temp = from.value;
from.value = to.value;
to.value = temp;
}
</script>
<template>
<div style="min-height:100vh;background:#0d1117;display:flex;align-items:center;justify-content:center;padding:1.5rem;font-family:system-ui,-apple-system,sans-serif">
<div style="width:100%;max-width:24rem;background:#161b22;border:1px solid #30363d;border-radius:1rem;padding:1.5rem">
<h2 style="color:#e6edf3;font-weight:700;font-size:1.125rem;margin:0 0 1.5rem">Currency Converter</h2>
<div style="display:flex;flex-direction:column;gap:0.75rem">
<!-- Amount input -->
<div style="background:#0d1117;border:1px solid #30363d;border-radius:0.75rem;padding:1rem">
<label style="display:block;font-size:0.6875rem;color:#8b949e;text-transform:uppercase;letter-spacing:0.08em;margin-bottom:0.5rem">Amount</label>
<div style="display:flex;align-items:center;gap:0.5rem">
<span style="color:#58a6ff;font-weight:700;font-size:1.125rem">{{ SYMBOLS[from] }}</span>
<input type="number" v-model="amount" min="0" style="flex:1;background:transparent;border:none;color:#e6edf3;font-size:1.5rem;font-weight:700;outline:none;font-variant-numeric:tabular-nums;width:100%" />
<select v-model="from" style="background:#21262d;border:1px solid #30363d;color:#e6edf3;border-radius:0.5rem;padding:0.25rem 0.5rem;font-size:0.875rem;outline:none">
<option v-for="c in currencies" :key="c" :value="c">{{ c }}</option>
</select>
</div>
</div>
<!-- Swap button -->
<div style="display:flex;justify-content:center">
<button @click="swap" style="width:2.25rem;height:2.25rem;background:#21262d;border:1px solid #30363d;border-radius:50%;display:flex;align-items:center;justify-content:center;color:#8b949e;cursor:pointer;font-size:1rem">โ
</button>
</div>
<!-- Converted output -->
<div style="background:#0d1117;border:1px solid #30363d;border-radius:0.75rem;padding:1rem">
<label style="display:block;font-size:0.6875rem;color:#8b949e;text-transform:uppercase;letter-spacing:0.08em;margin-bottom:0.5rem">Converted</label>
<div style="display:flex;align-items:center;gap:0.5rem">
<span style="color:#7ee787;font-weight:700;font-size:1.125rem">{{ SYMBOLS[to] }}</span>
<p style="flex:1;color:#e6edf3;font-size:1.5rem;font-weight:700;margin:0;font-variant-numeric:tabular-nums">{{ converted }}</p>
<select v-model="to" style="background:#21262d;border:1px solid #30363d;color:#e6edf3;border-radius:0.5rem;padding:0.25rem 0.5rem;font-size:0.875rem;outline:none">
<option v-for="c in currencies" :key="c" :value="c">{{ c }}</option>
</select>
</div>
</div>
</div>
<p style="text-align:center;font-size:0.6875rem;color:#484f58;margin-top:1rem">
1 {{ from }} = {{ rate }} {{ to }} · Indicative rates
</p>
</div>
</div>
</template><script>
const RATES = {
USD: 1,
EUR: 0.92,
GBP: 0.79,
JPY: 149.5,
CAD: 1.36,
AUD: 1.53,
CHF: 0.89,
CNY: 7.24,
MXN: 17.15,
BRL: 4.97,
};
const SYMBOLS = {
USD: "$",
EUR: "\u20AC",
GBP: "\u00A3",
JPY: "\u00A5",
CAD: "CA$",
AUD: "A$",
CHF: "Fr",
CNY: "\u00A5",
MXN: "$",
BRL: "R$",
};
const currencies = Object.keys(RATES);
let amount = "1";
let from = "USD";
let to = "EUR";
$: converted = (() => {
const val = (parseFloat(amount || "0") / RATES[from]) * RATES[to];
return isNaN(val)
? "\u2014"
: val.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
})();
$: rate = (RATES[to] / RATES[from]).toFixed(4);
function swap() {
const temp = from;
from = to;
to = temp;
}
</script>
<div style="min-height:100vh;background:#0b1120;display:flex;align-items:center;justify-content:center;padding:1rem;font-family:system-ui,-apple-system,sans-serif">
<div style="width:100%;max-width:380px;background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.08);border-radius:1rem;padding:1.25rem;backdrop-filter:blur(20px)">
<h2 style="color:#e6edf3;font-weight:700;font-size:0.875rem;margin:0 0 1rem">Currency Converter</h2>
<div style="display:flex;flex-direction:column;gap:0.75rem">
<!-- Amount -->
<div style="background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.08);border-radius:0.75rem;padding:0.875rem">
<label style="display:block;font-size:0.625rem;color:#64748b;text-transform:uppercase;letter-spacing:0.06em;font-weight:600;margin-bottom:0.375rem">Amount</label>
<div style="display:flex;align-items:center;gap:0.5rem">
<span style="color:#60a5fa;font-weight:700;font-size:1rem">{SYMBOLS[from]}</span>
<input type="number" bind:value={amount} min="0" style="flex:1;background:transparent;border:none;color:#e6edf3;font-size:1.25rem;font-weight:700;outline:none;font-variant-numeric:tabular-nums;min-width:0" />
<select bind:value={from} style="background:#161b22;border:1px solid rgba(255,255,255,0.08);color:#e6edf3;border-radius:0.5rem;padding:0.375rem 0.625rem;font-size:0.75rem;font-weight:500;outline:none;cursor:pointer">
{#each currencies as c}
<option value={c}>{c}</option>
{/each}
</select>
</div>
</div>
<!-- Swap -->
<div style="display:flex;justify-content:center">
<button on:click={swap} style="width:2rem;height:2rem;background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.08);border-radius:0.5rem;display:flex;align-items:center;justify-content:center;color:#60a5fa;cursor:pointer;font-size:0.875rem">{'\u21C5'}</button>
</div>
<!-- Converted -->
<div style="background:linear-gradient(135deg,rgba(96,165,250,0.08),rgba(52,211,153,0.06));border:1px solid rgba(96,165,250,0.15);border-radius:0.75rem;padding:0.875rem">
<label style="display:block;font-size:0.625rem;color:#64748b;text-transform:uppercase;letter-spacing:0.06em;font-weight:600;margin-bottom:0.375rem">Converted</label>
<div style="display:flex;align-items:center;gap:0.5rem">
<span style="color:#34d399;font-weight:700;font-size:1rem">{SYMBOLS[to]}</span>
<p style="flex:1;color:#e6edf3;font-size:1.25rem;font-weight:700;margin:0;font-variant-numeric:tabular-nums;min-width:0">{converted}</p>
<select bind:value={to} style="background:#161b22;border:1px solid rgba(255,255,255,0.08);color:#e6edf3;border-radius:0.5rem;padding:0.375rem 0.625rem;font-size:0.75rem;font-weight:500;outline:none;cursor:pointer">
{#each currencies as c}
<option value={c}>{c}</option>
{/each}
</select>
</div>
</div>
</div>
<p style="text-align:center;font-size:0.625rem;color:#475569;margin-top:0.75rem">
1 {from} = {rate} {to} · Indicative rates
</p>
</div>
</div>Currency Converter
A reliable currency conversion widget. It features a clean selection interface, automatic conversion on input, and can be integrated with external exchange rate APIs.
Features
- Support for multiple world currencies
- Real-time conversion
- Interactive currency selector
- Swap currencies functionality
- Precise decimal handling