Build a Hex to RGB Converter 🎨
Web developers see #FFFFFF daily.
But computers see rgb(255, 255, 255).
Hexadecimal is just a base-16 representation of RGB.
- Red: First 2 digits
- Green: Middle 2 digits
- Blue: Last 2 digits
Step 1: The Math (Base-16 to Base-10) 🧮
FF in Hex = 255 in Decimal.
00 in Hex = 0 in Decimal.
Logic:
- Strip the
#. - Split string into three 2-char chunks.
- Parse each chunk from Base 16.
const hexToRgb = (hex) => {
// Remove # if present
hex = hex.replace(/^#/, '');
// Handle shorthand (e.g., "FFF") -> Expand to "FFFFFF"
if (hex.length === 3) {
hex = hex.split('').map(c => c + c).join('');
}
const r = parseInt(hex.substring(0, 2), 16);
const g = parseInt(hex.substring(2, 4), 16);
const b = parseInt(hex.substring(4, 6), 16);
return `rgb(${r}, ${g}, ${b})`;
}
Step 2: The React Component 💻
We build a simple "Live Converter" that updates the background color as you type.
"use client"
import { useState } from "react"
import { Palette, Copy } from "lucide-react"
export default function HexConverter() {
const [hex, setHex] = useState("#3B82F6") // Default blue
const [rgb, setRgb] = useState("rgb(59, 130, 246)")
const [error, setError] = useState(false)
const handleHexChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const val = e.target.value;
setHex(val);
// Regex for valid Hex (3 or 6 chars, optional #)
const isValid = /^#?([0-9A-F]{3}){1,2}$/i.test(val);
if(isValid) {
setError(false);
// Reuse logic from Step 1
const clean = val.replace('#', '');
const fullHex = clean.length === 3
? clean.split('').map(c => c+c).join('')
: clean;
const r = parseInt(fullHex.substring(0,2), 16);
const g = parseInt(fullHex.substring(2,4), 16);
const b = parseInt(fullHex.substring(4,6), 16);
setRgb(`rgb(${r}, ${g}, ${b})`);
} else {
setError(true);
}
}
return (
<div className="max-w-md mx-auto p-6 bg-white border rounded-xl shadow-lg">
<div
className="h-32 rounded-lg mb-6 shadow-inner transition-colors duration-500"
style={{ backgroundColor: error ? '#fff' : hex }}
/>
<div className="flex gap-4 mb-4">
<div className="flex-1">
<label className="text-xs font-bold text-slate-500 uppercase">Hex Input</label>
<div className="relative">
<Palette className="absolute left-3 top-3 text-slate-400" size={16} />
<input
value={hex} onChange={handleHexChange}
className={`w-full p-2 pl-9 border rounded font-mono uppercase ${error ? 'border-red-500 text-red-500' : 'border-slate-300'}`}
maxLength={7}
/>
</div>
</div>
</div>
<div className="bg-slate-50 p-4 rounded-lg border border-slate-200 flex justify-between items-center">
<div>
<div className="text-xs text-slate-400 uppercase font-bold">RGB Output</div>
<div className="font-mono text-lg font-medium text-slate-700">{rgb}</div>
</div>
<button
onClick={() => navigator.clipboard.writeText(rgb)}
className="p-2 hover:bg-slate-200 rounded transition"
aria-label="Copy RGB"
>
<Copy size={18} className="text-slate-500" />
</button>
</div>
</div>
)
}
Step 3: Shorthand Hex Logic (#FFF) ✂️
A common bug in converters is ignoring shorthand.
#FFF (White) is valid CSS.
It is NOT 000FFF. It effectively repeats digits: F becomes FF.
Always check logic: if (hex.length === 3) expand().