UI Components Medium
Unit Converter
A comprehensive unit conversion tool for length, weight, and temperature. Featuring a clean, tabbed interface.
Open in Lab
MCP
vanilla-js css react tailwind vue svelte
Targets: TS JS HTML React Vue Svelte
Code
:root {
--unit-primary: #a78bfa;
--unit-primary-glow: rgba(167, 139, 250, 0.2);
--unit-bg: rgba(255, 255, 255, 0.04);
--unit-border: rgba(255, 255, 255, 0.08);
--unit-text: #f8fafc;
--unit-muted: #94a3b8;
--unit-tab-active: rgba(167, 139, 250, 0.12);
--unit-input-bg: rgba(255, 255, 255, 0.05);
--unit-result-bg: rgba(167, 139, 250, 0.06);
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
min-height: 100vh;
display: grid;
place-items: center;
background: #0b1221;
padding: 1rem;
}
.unit-widget {
background: var(--unit-bg);
border: 1px solid var(--unit-border);
border-radius: 20px;
overflow: hidden;
max-width: 400px;
margin: 0 auto;
font-family: "Inter", system-ui, sans-serif;
backdrop-filter: blur(16px);
}
.unit-tabs {
display: flex;
background: rgba(0, 0, 0, 0.2);
padding: 0.5rem;
gap: 0.375rem;
border-bottom: 1px solid var(--unit-border);
}
.unit-tab {
flex: 1;
padding: 0.6rem 0.5rem;
border: none;
background: transparent;
color: var(--unit-muted);
font-weight: 600;
cursor: pointer;
border-radius: 10px;
transition: all 0.2s;
font-size: 0.813rem;
letter-spacing: 0.01em;
}
.unit-tab.active {
background: var(--unit-tab-active);
color: var(--unit-primary);
border: 1px solid rgba(167, 139, 250, 0.2);
}
.unit-body {
padding: 1.5rem;
display: flex;
flex-direction: column;
gap: 1rem;
}
.unit-input-group {
display: flex;
gap: 0.5rem;
}
.unit-input-group input {
flex: 2;
padding: 0.75rem 1rem;
border: 1px solid var(--unit-border);
border-radius: 12px;
font-size: 1.125rem;
outline: none;
width: 100%;
background: var(--unit-input-bg);
color: var(--unit-text);
transition: all 0.2s;
font-weight: 600;
}
.unit-input-group select {
flex: 1;
padding: 0.75rem;
border: 1px solid var(--unit-border);
border-radius: 12px;
background: var(--unit-input-bg);
color: var(--unit-text);
cursor: pointer;
outline: none;
transition: all 0.2s;
}
.unit-input-group select option {
background: #1e293b;
}
.unit-separator {
text-align: center;
font-size: 1.5rem;
font-weight: 700;
color: var(--unit-primary);
line-height: 1;
}
input:focus,
select:focus {
border-color: rgba(167, 139, 250, 0.5);
box-shadow: 0 0 0 3px var(--unit-primary-glow);
}
#unit-to-val {
background: var(--unit-result-bg);
border-color: rgba(167, 139, 250, 0.2);
cursor: default;
color: var(--unit-primary);
}const units = {
length: {
meters: 1,
kilometers: 1000,
centimeters: 0.01,
millimeters: 0.001,
miles: 1609.34,
feet: 0.3048,
inches: 0.0254,
},
weight: {
kilograms: 1,
grams: 0.001,
milligrams: 0.000001,
pounds: 0.453592,
ounces: 0.0283495,
},
temperature: {
celsius: "C",
fahrenheit: "F",
kelvin: "K",
},
};
let currentCategory = "length";
const fromValEl = document.getElementById("unit-from-val");
const toValEl = document.getElementById("unit-to-val");
const fromTypeEl = document.getElementById("unit-from-type");
const toTypeEl = document.getElementById("unit-to-type");
const tabs = document.querySelectorAll(".unit-tab");
function populateSelects() {
const categoryUnits = units[currentCategory];
fromTypeEl.innerHTML = "";
toTypeEl.innerHTML = "";
Object.keys(categoryUnits).forEach((unit) => {
const opt1 = new Option(unit.charAt(0).toUpperCase() + unit.slice(1), unit);
const opt2 = new Option(unit.charAt(0).toUpperCase() + unit.slice(1), unit);
fromTypeEl.add(opt1);
toTypeEl.add(opt2);
});
if (toTypeEl.options.length > 1) {
toTypeEl.selectedIndex = 1;
}
}
function convert() {
const value = parseFloat(fromValEl.value) || 0;
const fromUnit = fromTypeEl.value;
const toUnit = toTypeEl.value;
if (currentCategory === "temperature") {
toValEl.value = convertTemperature(value, fromUnit, toUnit);
} else {
const categoryUnits = units[currentCategory];
const valueInBase = value * categoryUnits[fromUnit];
const result = valueInBase / categoryUnits[toUnit];
toValEl.value = parseFloat(result.toFixed(6));
}
}
function convertTemperature(value, from, to) {
let celsius;
if (from === "celsius") celsius = value;
else if (from === "fahrenheit") celsius = ((value - 32) * 5) / 9;
else if (from === "kelvin") celsius = value - 273.15;
let result;
if (to === "celsius") result = celsius;
else if (to === "fahrenheit") result = (celsius * 9) / 5 + 32;
else if (to === "kelvin") result = celsius + 273.15;
return parseFloat(result.toFixed(2));
}
// Event Listeners
tabs.forEach((tab) => {
tab.addEventListener("click", () => {
tabs.forEach((t) => t.classList.remove("active"));
tab.classList.add("active");
currentCategory = tab.dataset.category;
populateSelects();
convert();
});
});
[fromValEl, fromTypeEl, toTypeEl].forEach((el) => {
el.addEventListener("input", convert);
});
// Init
populateSelects();
convert();<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Unit 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="unit-widget">
<div class="unit-tabs">
<button class="unit-tab active" data-category="length">Length</button>
<button class="unit-tab" data-category="weight">Weight</button>
<button class="unit-tab" data-category="temperature">Temp</button>
</div>
<div class="unit-body">
<div class="unit-input-group">
<input type="number" id="unit-from-val" value="1" />
<select id="unit-from-type"></select>
</div>
<div class="unit-separator">=</div>
<div class="unit-input-group">
<input type="number" id="unit-to-val" readonly />
<select id="unit-to-type"></select>
</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>import { useState } from "react";
type Category = "length" | "weight" | "temperature";
const UNITS: Record<
Category,
{ label: string; toBase: (v: number) => number; fromBase: (v: number) => number }[]
> = {
length: [
{ label: "Meters", toBase: (v) => v, fromBase: (v) => v },
{ label: "Kilometers", toBase: (v) => v * 1000, fromBase: (v) => v / 1000 },
{ label: "Miles", toBase: (v) => v * 1609.34, fromBase: (v) => v / 1609.34 },
{ label: "Feet", toBase: (v) => v * 0.3048, fromBase: (v) => v / 0.3048 },
{ label: "Inches", toBase: (v) => v * 0.0254, fromBase: (v) => v / 0.0254 },
{ label: "Centimeters", toBase: (v) => v / 100, fromBase: (v) => v * 100 },
],
weight: [
{ label: "Kilograms", toBase: (v) => v, fromBase: (v) => v },
{ label: "Grams", toBase: (v) => v / 1000, fromBase: (v) => v * 1000 },
{ label: "Pounds", toBase: (v) => v * 0.453592, fromBase: (v) => v / 0.453592 },
{ label: "Ounces", toBase: (v) => v * 0.0283495, fromBase: (v) => v / 0.0283495 },
{ label: "Tons", toBase: (v) => v * 1000, fromBase: (v) => v / 1000 },
],
temperature: [
{ label: "Celsius", toBase: (v) => v, fromBase: (v) => v },
{ label: "Fahrenheit", toBase: (v) => (v - 32) * (5 / 9), fromBase: (v) => v * (9 / 5) + 32 },
{ label: "Kelvin", toBase: (v) => v - 273.15, fromBase: (v) => v + 273.15 },
],
};
const CATEGORIES: Category[] = ["length", "weight", "temperature"];
export default function UnitConverterRC() {
const [category, setCategory] = useState<Category>("length");
const [value, setValue] = useState("1");
const [fromIdx, setFromIdx] = useState(0);
const [toIdx, setToIdx] = useState(1);
const units = UNITS[category];
const fromUnit = units[fromIdx];
const toUnit = units[toIdx];
const numVal = parseFloat(value || "0");
const converted = toUnit.fromBase(fromUnit.toBase(numVal));
function setcat(c: Category) {
setCategory(c);
setFromIdx(0);
setToIdx(1);
}
return (
<div className="min-h-screen bg-[#0d1117] flex items-center justify-center p-6">
<div className="w-full max-w-sm bg-[#161b22] border border-[#30363d] rounded-2xl p-6">
<h2 className="text-[#e6edf3] font-bold text-lg mb-4">Unit Converter</h2>
<div className="flex gap-2 mb-5">
{CATEGORIES.map((c) => (
<button
key={c}
onClick={() => setcat(c)}
className={`flex-1 py-1.5 rounded-lg text-xs font-semibold capitalize transition-colors ${
category === c
? "bg-[#58a6ff] text-[#0d1117]"
: "bg-[#21262d] text-[#8b949e] hover:text-[#e6edf3]"
}`}
>
{c}
</button>
))}
</div>
<div className="space-y-3">
<div className="bg-[#0d1117] border border-[#30363d] rounded-xl p-4">
<select
value={fromIdx}
onChange={(e) => setFromIdx(Number(e.target.value))}
className="w-full bg-transparent text-[#8b949e] text-xs mb-2 focus:outline-none"
>
{units.map((u, i) => (
<option key={u.label} value={i}>
{u.label}
</option>
))}
</select>
<input
type="number"
value={value}
onChange={(e) => setValue(e.target.value)}
className="w-full bg-transparent text-[#e6edf3] text-3xl font-bold focus:outline-none tabular-nums"
/>
</div>
<div className="flex justify-center text-[#484f58]">↓</div>
<div className="bg-[#0d1117] border border-[#30363d] rounded-xl p-4">
<select
value={toIdx}
onChange={(e) => setToIdx(Number(e.target.value))}
className="w-full bg-transparent text-[#8b949e] text-xs mb-2 focus:outline-none"
>
{units.map((u, i) => (
<option key={u.label} value={i}>
{u.label}
</option>
))}
</select>
<p className="text-[#7ee787] text-3xl font-bold tabular-nums">
{isNaN(converted) ? "—" : parseFloat(converted.toPrecision(8)).toLocaleString()}
</p>
</div>
</div>
</div>
</div>
);
}<script setup>
import { ref, computed } from "vue";
const UNITS = {
length: [
{ label: "Meters", toBase: (v) => v, fromBase: (v) => v },
{ label: "Kilometers", toBase: (v) => v * 1000, fromBase: (v) => v / 1000 },
{ label: "Miles", toBase: (v) => v * 1609.34, fromBase: (v) => v / 1609.34 },
{ label: "Feet", toBase: (v) => v * 0.3048, fromBase: (v) => v / 0.3048 },
{ label: "Inches", toBase: (v) => v * 0.0254, fromBase: (v) => v / 0.0254 },
{ label: "Centimeters", toBase: (v) => v / 100, fromBase: (v) => v * 100 },
],
weight: [
{ label: "Kilograms", toBase: (v) => v, fromBase: (v) => v },
{ label: "Grams", toBase: (v) => v / 1000, fromBase: (v) => v * 1000 },
{ label: "Pounds", toBase: (v) => v * 0.453592, fromBase: (v) => v / 0.453592 },
{ label: "Ounces", toBase: (v) => v * 0.0283495, fromBase: (v) => v / 0.0283495 },
{ label: "Tons", toBase: (v) => v * 1000, fromBase: (v) => v / 1000 },
],
temperature: [
{ label: "Celsius", toBase: (v) => v, fromBase: (v) => v },
{ label: "Fahrenheit", toBase: (v) => (v - 32) * (5 / 9), fromBase: (v) => v * (9 / 5) + 32 },
{ label: "Kelvin", toBase: (v) => v - 273.15, fromBase: (v) => v + 273.15 },
],
};
const CATEGORIES = ["length", "weight", "temperature"];
const category = ref("length");
const value = ref("1");
const fromIdx = ref(0);
const toIdx = ref(1);
const units = computed(() => UNITS[category.value]);
const fromUnit = computed(() => units.value[fromIdx.value]);
const toUnit = computed(() => units.value[toIdx.value]);
const numVal = computed(() => parseFloat(value.value || "0"));
const converted = computed(() => toUnit.value.fromBase(fromUnit.value.toBase(numVal.value)));
const display = computed(() =>
isNaN(converted.value) ? "—" : parseFloat(converted.value.toPrecision(8)).toLocaleString()
);
function setcat(c) {
category.value = c;
fromIdx.value = 0;
toIdx.value = 1;
}
</script>
<template>
<div class="min-h-screen bg-[#0d1117] flex items-center justify-center p-6">
<div class="w-full max-w-sm bg-[#161b22] border border-[#30363d] rounded-2xl p-6">
<h2 class="text-[#e6edf3] font-bold text-lg mb-4">Unit Converter</h2>
<div class="flex gap-2 mb-5">
<button
v-for="c in CATEGORIES"
:key="c"
@click="setcat(c)"
:class="[
'flex-1 py-1.5 rounded-lg text-xs font-semibold capitalize transition-colors',
category === c
? 'bg-[#58a6ff] text-[#0d1117]'
: 'bg-[#21262d] text-[#8b949e] hover:text-[#e6edf3]'
]"
>
{{ c }}
</button>
</div>
<div class="space-y-3">
<div class="bg-[#0d1117] border border-[#30363d] rounded-xl p-4">
<select
:value="fromIdx"
@change="fromIdx = Number($event.target.value)"
class="w-full bg-transparent text-[#8b949e] text-xs mb-2 focus:outline-none"
>
<option v-for="(u, i) in units" :key="u.label" :value="i">{{ u.label }}</option>
</select>
<input
type="number"
v-model="value"
class="w-full bg-transparent text-[#e6edf3] text-3xl font-bold focus:outline-none tabular-nums"
/>
</div>
<div class="flex justify-center text-[#484f58]">↓</div>
<div class="bg-[#0d1117] border border-[#30363d] rounded-xl p-4">
<select
:value="toIdx"
@change="toIdx = Number($event.target.value)"
class="w-full bg-transparent text-[#8b949e] text-xs mb-2 focus:outline-none"
>
<option v-for="(u, i) in units" :key="u.label" :value="i">{{ u.label }}</option>
</select>
<p class="text-[#7ee787] text-3xl font-bold tabular-nums">
{{ display }}
</p>
</div>
</div>
</div>
</div>
</template><script>
const UNITS = {
length: [
{ label: "Meters", toBase: (v) => v, fromBase: (v) => v },
{ label: "Kilometers", toBase: (v) => v * 1000, fromBase: (v) => v / 1000 },
{ label: "Miles", toBase: (v) => v * 1609.34, fromBase: (v) => v / 1609.34 },
{ label: "Feet", toBase: (v) => v * 0.3048, fromBase: (v) => v / 0.3048 },
{ label: "Inches", toBase: (v) => v * 0.0254, fromBase: (v) => v / 0.0254 },
{ label: "Centimeters", toBase: (v) => v / 100, fromBase: (v) => v * 100 },
],
weight: [
{ label: "Kilograms", toBase: (v) => v, fromBase: (v) => v },
{ label: "Grams", toBase: (v) => v / 1000, fromBase: (v) => v * 1000 },
{ label: "Pounds", toBase: (v) => v * 0.453592, fromBase: (v) => v / 0.453592 },
{ label: "Ounces", toBase: (v) => v * 0.0283495, fromBase: (v) => v / 0.0283495 },
{ label: "Tons", toBase: (v) => v * 1000, fromBase: (v) => v / 1000 },
],
temperature: [
{ label: "Celsius", toBase: (v) => v, fromBase: (v) => v },
{ label: "Fahrenheit", toBase: (v) => (v - 32) * (5 / 9), fromBase: (v) => v * (9 / 5) + 32 },
{ label: "Kelvin", toBase: (v) => v - 273.15, fromBase: (v) => v + 273.15 },
],
};
const CATEGORIES = ["length", "weight", "temperature"];
let category = "length";
let value = "1";
let fromIdx = 0;
let toIdx = 1;
$: units = UNITS[category];
$: fromUnit = units[fromIdx];
$: toUnit = units[toIdx];
$: numVal = parseFloat(value || "0");
$: converted = toUnit.fromBase(fromUnit.toBase(numVal));
$: display = isNaN(converted) ? "—" : parseFloat(converted.toPrecision(8)).toLocaleString();
function setcat(c) {
category = c;
fromIdx = 0;
toIdx = 1;
}
</script>
<div class="min-h-screen bg-[#0d1117] flex items-center justify-center p-6">
<div class="w-full max-w-sm bg-[#161b22] border border-[#30363d] rounded-2xl p-6">
<h2 class="text-[#e6edf3] font-bold text-lg mb-4">Unit Converter</h2>
<div class="flex gap-2 mb-5">
{#each CATEGORIES as c}
<button
on:click={() => setcat(c)}
class="flex-1 py-1.5 rounded-lg text-xs font-semibold capitalize transition-colors {category === c
? 'bg-[#58a6ff] text-[#0d1117]'
: 'bg-[#21262d] text-[#8b949e] hover:text-[#e6edf3]'}"
>
{c}
</button>
{/each}
</div>
<div class="space-y-3">
<div class="bg-[#0d1117] border border-[#30363d] rounded-xl p-4">
<select
bind:value={fromIdx}
class="w-full bg-transparent text-[#8b949e] text-xs mb-2 focus:outline-none"
>
{#each units as u, i}
<option value={i}>{u.label}</option>
{/each}
</select>
<input
type="number"
bind:value={value}
class="w-full bg-transparent text-[#e6edf3] text-3xl font-bold focus:outline-none tabular-nums"
/>
</div>
<div class="flex justify-center text-[#484f58]">↓</div>
<div class="bg-[#0d1117] border border-[#30363d] rounded-xl p-4">
<select
bind:value={toIdx}
class="w-full bg-transparent text-[#8b949e] text-xs mb-2 focus:outline-none"
>
{#each units as u, i}
<option value={i}>{u.label}</option>
{/each}
</select>
<p class="text-[#7ee787] text-3xl font-bold tabular-nums">
{display}
</p>
</div>
</div>
</div>
</div>Unit Converter
An all-in-one unit conversion widget. Easily switch between different measurement types and get immediate results.
Features
- Multiple categories: Length, Weight, Temperature
- Bi-directional conversion
- Clean, tabbed navigation
- Precision adjustments
- Mobile-friendly design