Color Converter: Convert HEX, RGB, and HSL Online — Complete Guide
Convert HEX to RGB, RGB to HSL, and more. Complete guide for CSS color formats, JavaScript libraries, accessibility contrast ratios, and design tokens.
Color conversion transforms the same color between representations: HEX (#3B82F6), RGB (rgb(59,130,246)), HSL (hsl(217,91%,60%)), and modern formats like OKLCH. HEX and RGB are mathematically equivalent; HSL separates hue from brightness for design workflows. CSS now natively supports oklch(), lab(), and color-mix(). For accessibility, WCAG AA requires 4.5:1 contrast ratio for normal text. Use chroma.js or tinycolor2 in JavaScript; colorsys in Python.
1. Color Formats Overview
Modern CSS and design tools support many color representations. Each format has different strengths:
| Format | Human Readable | CSS Support | Perceptual | Best For |
|---|---|---|---|---|
| HEX | Medium | Universal | No | HTML/CSS shorthand, design specs |
| RGB | Medium | Universal | No | Screen rendering, canvas, WebGL |
| HSL | High | Universal | Partial | Design systems, theming, dark mode |
| HSB/HSV | High | None (JS only) | Partial | Color pickers (Figma, Photoshop) |
| OKLCH | Medium | Modern browsers | Yes | Generated palettes, gradients |
| Named Colors | Highest | Universal | No | Prototyping, readability |
CSS Level 4 and 5 introduce powerful new color functions. oklch(), lab(), and lch() are perceptually uniform spaces. color-mix() (CSS Level 5) blends colors. color(display-p3 r g b) accesses wider gamuts on supported displays.
2. HEX ↔ RGB Conversion
HEX is simply RGB encoded in base-16. The 6-character code splits into three 2-character pairs, each representing a channel value from 00 (0) to FF (255).
HEX to RGB
Parse each 2-character pair with parseInt(hex, 16):
function hexToRgb(hex) {
// Remove # prefix
const clean = hex.replace(/^#/, '');
// Expand 3-char shorthand: #abc → #aabbcc
const full = clean.length === 3
? clean.split('').map(c => c + c).join('')
: clean;
const r = parseInt(full.slice(0, 2), 16); // 0–255
const g = parseInt(full.slice(2, 4), 16);
const b = parseInt(full.slice(4, 6), 16);
// Handle alpha from 8-char HEX (#rrggbbaa)
const a = full.length === 8
? Math.round(parseInt(full.slice(6, 8), 16) / 255 * 100) / 100
: 1;
return { r, g, b, a };
}
// Example
hexToRgb('#3B82F6'); // { r: 59, g: 130, b: 246, a: 1 }
hexToRgb('#F00'); // { r: 255, g: 0, b: 0, a: 1 }
hexToRgb('#3B82F680'); // { r: 59, g: 130, b: 246, a: 0.5 }RGB to HEX
Combine channels using bitwise shift, then convert to a hex string:
function rgbToHex(r, g, b) {
return '#' + (r << 16 | g << 8 | b)
.toString(16)
.padStart(6, '0')
.toUpperCase();
}
// With alpha (produces 8-char HEX)
function rgbaToHex(r, g, b, a = 1) {
const alpha = Math.round(a * 255)
.toString(16)
.padStart(2, '0');
return rgbToHex(r, g, b) + alpha.toUpperCase();
}
// Examples
rgbToHex(59, 130, 246); // '#3B82F6'
rgbToHex(255, 0, 0); // '#FF0000'
rgbaToHex(59, 130, 246, 0.5); // '#3B82F680'Short HEX (#abc → #aabbcc): Duplicate each character. This is a lossless expansion — #abc is identical to #aabbcc.
Alpha HEX (#rrggbbaa): The 8-character form appends an alpha channel where FF = fully opaque and 00 = fully transparent. Supported in all modern browsers.
3. RGB ↔ HSL Conversion
HSL separates color into Hue (0–360°), Saturation (0–100%), and Lightness (0–100%). The conversion normalizes RGB to [0,1] and finds the min/max channels.
RGB to HSL
function rgbToHsl(r, g, b) {
// Normalize to [0, 1]
r /= 255; g /= 255; b /= 255;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
const delta = max - min;
// Lightness
const l = (max + min) / 2;
// Saturation
let s = 0;
if (delta !== 0) {
s = delta / (1 - Math.abs(2 * l - 1));
}
// Hue (degrees)
let h = 0;
if (delta !== 0) {
if (max === r) h = ((g - b) / delta) % 6;
else if (max === g) h = (b - r) / delta + 2;
else h = (r - g) / delta + 4;
h = Math.round(h * 60);
if (h < 0) h += 360;
}
return {
h,
s: Math.round(s * 100),
l: Math.round(l * 100),
};
}
// Example: rgb(255, 0, 0) → hsl(0, 100%, 50%)
rgbToHsl(255, 0, 0); // { h: 0, s: 100, l: 50 }
rgbToHsl(59, 130, 246); // { h: 217, s: 91, l: 60 }HSL to RGB
function hslToRgb(h, s, l) {
s /= 100; l /= 100;
const c = (1 - Math.abs(2 * l - 1)) * s; // Chroma
const x = c * (1 - Math.abs((h / 60) % 2 - 1));
const m = l - c / 2;
let r = 0, g = 0, b = 0;
if (h < 60) { r = c; g = x; b = 0; }
else if (h < 120) { r = x; g = c; b = 0; }
else if (h < 180) { r = 0; g = c; b = x; }
else if (h < 240) { r = 0; g = x; b = c; }
else if (h < 300) { r = x; g = 0; b = c; }
else { r = c; g = 0; b = x; }
return {
r: Math.round((r + m) * 255),
g: Math.round((g + m) * 255),
b: Math.round((b + m) * 255),
};
}
// Example: hsl(0, 100%, 50%) → rgb(255, 0, 0)
hslToRgb(0, 100, 50); // { r: 255, g: 0, b: 0 }
hslToRgb(217, 91, 60); // { r: 59, g: 130, b: 246 }4. CSS Color Functions
CSS has evolved significantly. Here are all major color functions with examples:
/* Legacy syntax */
color: rgb(59, 130, 246);
color: rgba(59, 130, 246, 0.5); /* alpha */
color: hsl(217, 91%, 60%);
color: hsla(217, 91%, 60%, 0.5); /* alpha */
/* Modern space-separated syntax (CSS Color Level 4) */
color: rgb(59 130 246);
color: rgb(59 130 246 / 0.5); /* alpha with / */
color: hsl(217 91% 60%);
color: hsl(217 91% 60% / 0.5);
/* OKLCH — perceptually uniform (CSS Color Level 4) */
/* oklch(lightness chroma hue) */
color: oklch(0.62 0.18 264); /* blue */
color: oklch(0.62 0.18 264 / 0.8); /* with alpha */
/* LAB — perceptually uniform */
color: lab(54% -38 57); /* CSS Color Level 4 */
/* LCH — cylindrical LAB */
color: lch(54% 67 134);
/* color-mix() — blend two colors (CSS Level 5) */
color: color-mix(in srgb, blue 30%, white);
color: color-mix(in oklch, #3B82F6, transparent 50%);
/* color() — access wide gamuts */
color: color(display-p3 0.2 0.5 0.9); /* P3 wide gamut */
color: color(srgb 0.23 0.51 0.96); /* explicit sRGB */
/* color-contrast() — experimental, picks best contrast */
color: color-contrast(white vs black, navy, #666);Browser support: rgb(), hsl(), and named colors work everywhere. oklch(), lab(), and color-mix() are supported in Chrome 111+, Firefox 113+, Safari 16.2+. color-contrast() is experimental (behind flags).
5. JavaScript — chroma.js
chroma.js is the most powerful JavaScript color library. It supports RGB, HSL, LAB, OKLCH, and can compute contrast ratios and generate scales.
// npm install chroma-js
import chroma from 'chroma-js';
/* ----- Basic Conversions ----- */
const c = chroma('#ff0000');
c.rgb(); // [255, 0, 0]
c.hsl(); // [0, 1, 0.5] (normalized 0–1)
c.oklch(); // [0.627, 0.257, 29.2]
c.hex(); // '#ff0000'
c.css(); // 'rgb(255,0,0)'
c.css('hsl'); // 'hsl(0,100%,50%)'
/* ----- Luminance & Contrast ----- */
chroma('#3B82F6').luminance(); // 0.177
chroma.contrast('#3B82F6', '#ffffff'); // 3.04
chroma.contrast('#1d4ed8', '#ffffff'); // 7.08 (WCAG AAA)
/* ----- Manipulate Colors ----- */
chroma('#3B82F6').darken(1).hex(); // '#1a5dcc'
chroma('#3B82F6').lighten(1).hex(); // '#87b8ff'
chroma('#3B82F6').saturate(2).hex(); // '#0074ff'
chroma('#3B82F6').desaturate(1).hex(); // '#5c8ec2'
chroma('#3B82F6').alpha(0.5).css(); // 'rgba(59,130,246,0.5)'
/* ----- Mix Colors ----- */
chroma.mix('#ff0000', '#0000ff', 0.5, 'rgb').hex(); // '#7f007f'
chroma.mix('#ff0000', '#0000ff', 0.5, 'lab').hex(); // '#c0004e'
chroma.mix('#ff0000', '#0000ff', 0.5, 'oklch').hex(); // perceptually even
/* ----- Color Scales ----- */
const scale = chroma.scale(['yellow', 'navy']).mode('lab');
scale(0).hex(); // '#ffff00' (yellow)
scale(0.5).hex(); // midpoint in LAB space
scale(1).hex(); // '#000080' (navy)
// Generate a palette of 7 colors
chroma.scale(['#eff6ff', '#1d4ed8']).colors(7);6. JavaScript — tinycolor2
tinycolor2 is a lightweight (5KB) library focused on simple color conversions and manipulation. It accepts any CSS color string as input.
// npm install tinycolor2
import tinycolor from 'tinycolor2';
/* ----- Format Conversions ----- */
tinycolor('red').toHexString(); // '#ff0000'
tinycolor('#3B82F6').toRgbString(); // 'rgb(59, 130, 246)'
tinycolor('#3B82F6').toHslString(); // 'hsl(217, 91%, 60%)'
tinycolor('#3B82F6').toHsvString(); // 'hsv(217, 76%, 96%)'
tinycolor('hsl(217, 91%, 60%)').toHexString(); // '#3b82f6'
/* ----- Detect Light/Dark ----- */
tinycolor('#3B82F6').isLight(); // false
tinycolor('#3B82F6').isDark(); // true
tinycolor('#93c5fd').isLight(); // true
tinycolor('#3B82F6').getBrightness(); // 118 (0–255)
/* ----- Modify Colors ----- */
tinycolor('#3B82F6').darken(10).toHexString(); // '#1d68de'
tinycolor('#3B82F6').lighten(20).toHexString(); // '#a8c8fb'
tinycolor('#3B82F6').spin(30).toHexString(); // hue +30° → teal-blue
tinycolor('#3B82F6').spin(-30).toHexString(); // hue -30° → purple
tinycolor('#3B82F6').greyscale().toHexString(); // '#808080'
/* ----- Transparency ----- */
tinycolor('#3B82F6').setAlpha(0.5).toRgbString(); // 'rgba(59,130,246,0.5)'
tinycolor('rgba(59,130,246,0.5)').getAlpha(); // 0.5
/* ----- Readability (WCAG) ----- */
tinycolor.readability('#3B82F6', '#ffffff'); // 3.04
tinycolor.isReadable('#1d4ed8', '#ffffff', { level: 'AA', size: 'small' }); // true
tinycolor.isReadable('#3B82F6', '#ffffff', { level: 'AAA' }); // false
/* ----- Color Harmony ----- */
tinycolor('#3B82F6').complement().toHexString(); // opposite hue
tinycolor('#3B82F6').splitcomplement()[1].toHexString();
tinycolor('#3B82F6').triad()[1].toHexString(); // +120°
tinycolor('#3B82F6').analogous()[1].toHexString(); // +30°7. CSS Custom Properties for Theming
CSS custom properties (variables) enable dynamic, maintainable color systems. HSL is particularly well-suited because its three axes map to independent design decisions: hue (brand identity), saturation (vibrancy), and lightness (tint/shade).
:root {
/* HSL-based token system */
--hue-primary: 217;
--hue-success: 142;
--hue-danger: 0;
/* Scale: adjust only L to create tints/shades */
--color-primary-50: hsl(var(--hue-primary) 91% 97%);
--color-primary-100: hsl(var(--hue-primary) 91% 93%);
--color-primary-200: hsl(var(--hue-primary) 91% 85%);
--color-primary-300: hsl(var(--hue-primary) 91% 73%);
--color-primary-400: hsl(var(--hue-primary) 91% 63%);
--color-primary-500: hsl(var(--hue-primary) 91% 56%); /* base */
--color-primary-600: hsl(var(--hue-primary) 91% 46%);
--color-primary-700: hsl(var(--hue-primary) 91% 36%);
--color-primary-800: hsl(var(--hue-primary) 91% 26%);
--color-primary-900: hsl(var(--hue-primary) 91% 16%);
/* Semantic tokens */
--color-text: hsl(var(--hue-primary) 20% 15%);
--color-bg: hsl(var(--hue-primary) 10% 99%);
--color-surface: hsl(var(--hue-primary) 10% 96%);
--color-border: hsl(var(--hue-primary) 20% 88%);
}
/* Dark mode override */
@media (prefers-color-scheme: dark) {
:root {
--color-text: hsl(var(--hue-primary) 20% 92%);
--color-bg: hsl(var(--hue-primary) 20% 8%);
--color-surface: hsl(var(--hue-primary) 20% 12%);
--color-border: hsl(var(--hue-primary) 20% 24%);
}
}
/* Using OKLCH for perceptually uniform tokens */
:root {
--color-primary: oklch(0.62 0.18 264);
--color-primary-light: oklch(0.82 0.10 264);
--color-primary-dark: oklch(0.42 0.20 264);
}
/* Component usage */
.button-primary {
background-color: var(--color-primary-500);
color: white;
border: 1px solid var(--color-primary-600);
}
.button-primary:hover {
background-color: var(--color-primary-600);
}8. Accessibility — WCAG Contrast Ratios
Color contrast affects readability for sighted users and is essential for users with low vision or color blindness (affecting ~8% of males). WCAG 2.1 defines contrast requirements based on relative luminance.
| WCAG Level | Normal Text | Large Text | UI Components |
|---|---|---|---|
| AA (Minimum) | 4.5:1 | 3:1 | 3:1 |
| AAA (Enhanced) | 7:1 | 4.5:1 | — |
Large text is defined as 18pt (24px) or 14pt (approximately 18.67px) bold.
Calculating Relative Luminance
// Step 1: Linearize sRGB values (gamma correction)
function linearize(val) {
const v = val / 255;
return v <= 0.03928
? v / 12.92
: Math.pow((v + 0.055) / 1.055, 2.4);
}
// Step 2: Apply BT.709 luminance coefficients
function relativeLuminance(r, g, b) {
const R = linearize(r);
const G = linearize(g);
const B = linearize(b);
return 0.2126 * R + 0.7152 * G + 0.0722 * B;
}
// Step 3: Compute contrast ratio
function contrastRatio(rgb1, rgb2) {
const L1 = relativeLuminance(...rgb1);
const L2 = relativeLuminance(...rgb2);
const lighter = Math.max(L1, L2);
const darker = Math.min(L1, L2);
return (lighter + 0.05) / (darker + 0.05);
}
// Examples
contrastRatio([59, 130, 246], [255, 255, 255]); // ~3.04 (fails AA)
contrastRatio([29, 78, 216], [255, 255, 255]); // ~7.08 (passes AAA)
contrastRatio([0, 0, 0], [255, 255, 255]); // 21:1 (maximum)CSS color-contrast() (experimental): A forthcoming CSS function that automatically picks the highest-contrast color from a list: color: color-contrast(white vs black, navy, #666). Currently behind flags in browsers.
9. Python — colorsys and Pillow
Python's standard library includes the colorsys module for color space conversions. All values are normalized to [0.0, 1.0].
import colorsys
# RGB to HLS (note: HLS = Hue, Lightness, Saturation — different order from HSL!)
r, g, b = 59/255, 130/255, 246/255
h, l, s = colorsys.rgb_to_hls(r, g, b)
print(f"H={h*360:.0f}° S={s*100:.0f}% L={l*100:.0f}%")
# H=217° S=91% L=60%
# RGB to HSV
h, s, v = colorsys.rgb_to_hsv(r, g, b)
print(f"H={h*360:.0f}° S={s*100:.0f}% V={v*100:.0f}%")
# H=217° S=76% V=96%
# HLS to RGB
r2, g2, b2 = colorsys.hls_to_rgb(0.603, 0.60, 0.91)
print(tuple(round(x*255) for x in (r2, g2, b2)))
# (59, 130, 246)
# YIQ conversion (NTSC luminance model)
y, i, q = colorsys.rgb_to_yiq(r, g, b)
print(f"Y (luminance)={y:.3f}")
# --- Pillow ---
from PIL import Image
img = Image.open('photo.jpg').convert('RGB')
pixel = img.getpixel((100, 100)) # (r, g, b) tuple
h, l, s = colorsys.rgb_to_hls(*[v/255 for v in pixel])
# Convert entire image to HSV for processing
import numpy as np
arr = np.array(img) / 255.0 # shape (H, W, 3)
# Vectorized conversion would use skimage or opencv
# --- matplotlib ---
import matplotlib.colors as mcolors
# HEX to RGB (0–1 range)
rgb = mcolors.to_rgb('#3B82F6') # (0.231, 0.510, 0.965)
# Named color to RGBA
rgba = mcolors.to_rgba('steelblue')
# HSV to RGB
rgb = mcolors.hsv_to_rgb([0.603, 0.76, 0.965])10. Design Token Workflow
Modern design systems separate color decisions into layers: primitive tokens (raw values), semantic tokens (contextual meaning), and component tokens (specific usage).
/* ===== Layer 1: Primitive Tokens (raw palette) ===== */
:root {
/* Blue scale */
--blue-50: #eff6ff;
--blue-100: #dbeafe;
--blue-200: #bfdbfe;
--blue-300: #93c5fd;
--blue-400: #60a5fa;
--blue-500: #3b82f6;
--blue-600: #2563eb;
--blue-700: #1d4ed8;
--blue-800: #1e40af;
--blue-900: #1e3a8a;
/* Neutral scale */
--neutral-50: #f8fafc;
--neutral-900: #0f172a;
}
/* ===== Layer 2: Semantic Tokens (meaning) ===== */
:root {
--color-action: var(--blue-600);
--color-action-hover: var(--blue-700);
--color-text-primary: var(--neutral-900);
--color-text-muted: #64748b;
--color-bg-primary: #ffffff;
--color-bg-subtle: var(--neutral-50);
--color-border: #e2e8f0;
}
@media (prefers-color-scheme: dark) {
:root {
--color-action: var(--blue-400);
--color-action-hover: var(--blue-300);
--color-text-primary: var(--neutral-50);
--color-bg-primary: var(--neutral-900);
--color-bg-subtle: #1e293b;
--color-border: #334155;
}
}
/* ===== Layer 3: Component Tokens ===== */
.button {
--btn-bg: var(--color-action);
--btn-bg-hover: var(--color-action-hover);
--btn-text: white;
background: var(--btn-bg);
color: var(--btn-text);
}
.button:hover { background: var(--btn-bg-hover); }From Figma to CSS: Export Figma color styles as JSON using the Tokens Studio plugin or Figma's REST API. Transform with Style Dictionary (Amazon's open-source tool) to generate CSS custom properties, Sass variables, or TypeScript constants from a single source of truth.
11. Color Spaces for Web
Understanding color spaces helps you choose the right format for each use case:
The universal web standard. All CSS colors default to sRGB. Supported in every browser, tool, and image format. Use for maximum compatibility.
Best for: Default choice for all web colors.
Wide gamut covering ~50% more colors than sRGB. Supported by iPhone cameras, Macs, and modern OLED monitors. Use color(display-p3 r g b) in CSS.
Best for: Vibrant photos, video content, Apple platforms.
Perceptually uniform — equal changes produce equal perceived differences. No hue shift when adjusting lightness. Ideal for generating accessible palettes programmatically.
Best for: Design tokens, gradient interpolation, palette generation.
/* Provide progressive enhancement with fallbacks */
.vivid-blue {
/* Fallback for older browsers */
color: #3B82F6;
/* P3 wide gamut (more vibrant on supported displays) */
@supports (color: color(display-p3 0 0 0)) {
color: color(display-p3 0.17 0.46 0.99);
}
}
/* Convert P3 to sRGB (values may clip outside sRGB gamut) */
/* #3B82F6 sRGB ≈ display-p3(0.2, 0.48, 0.96) */12. Color Palette Generation
Generate color harmonies by rotating the hue axis in HSL. The following patterns are standard in color theory:
| Harmony | Hue Offsets | Description |
|---|---|---|
| Complementary | +180° | High contrast, opposite colors on wheel |
| Triadic | +120°, +240° | Three evenly spaced hues, vibrant |
| Analogous | ±30° | Adjacent hues, harmonious and calm |
| Split-Complementary | +150°, +210° | Softer contrast than complementary |
| Monochromatic | same hue | Vary only L (lightness) in HSL |
// Generate color harmonies from a base HSL color
function generateHarmonies(h, s, l) {
const wrap = (deg) => ((deg % 360) + 360) % 360;
return {
base: `hsl(${h} ${s}% ${l}%)`,
complementary: `hsl(${wrap(h + 180)} ${s}% ${l}%)`,
triadic: [`hsl(${wrap(h + 120)} ${s}% ${l}%)`,
`hsl(${wrap(h + 240)} ${s}% ${l}%)`],
analogous: [`hsl(${wrap(h - 30)} ${s}% ${l}%)`,
`hsl(${wrap(h + 30)} ${s}% ${l}%)`],
splitComp: [`hsl(${wrap(h + 150)} ${s}% ${l}%)`,
`hsl(${wrap(h + 210)} ${s}% ${l}%)`],
};
}
// Generate a tint/shade scale (monochromatic)
function generateScale(h, s, steps = 9) {
return Array.from({ length: steps }, (_, i) => {
// Lightness from 95% (lightest) to 15% (darkest)
const l = 95 - i * (80 / (steps - 1));
return `hsl(${h} ${s}% ${Math.round(l)}%)`;
});
}
// Example: Blue (hue=217, saturation=91%)
const scale = generateScale(217, 91);
// ['hsl(217 91% 95%)', 'hsl(217 91% 85%)', ... , 'hsl(217 91% 15%)']
// In OKLCH — perceptually even scale
function oklchScale(l_start, l_end, c, h, steps) {
return Array.from({ length: steps }, (_, i) => {
const l = l_start + (l_end - l_start) * (i / (steps - 1));
return `oklch(${l.toFixed(2)} ${c} ${h})`;
});
}
const blueScale = oklchScale(0.95, 0.25, 0.18, 264, 9);- HEX and RGB are mathematically equivalent — conversion is lossless and straightforward.
- HSL separates hue from brightness, making it ideal for design systems and dark mode theming.
- OKLCH is perceptually uniform: adjusting lightness does not shift the perceived hue — use it for generated palettes and gradients.
- CSS now natively supports
oklch(),lab(),lch(),color-mix(), andcolor(display-p3)in modern browsers. - WCAG AA requires 4.5:1 contrast for normal text; AAA requires 7:1. Use relative luminance to calculate.
- Use chroma.js for advanced color science (scales, LAB interpolation, contrast ratios); tinycolor2 for simple conversions and dark/light detection.
- Python's
colorsysmodule provides HLS and HSV conversions; note it uses HLS order (Hue, Lightness, Saturation), not HSL. - Design token systems separate primitive colors from semantic meaning, enabling consistent theming across light/dark modes and components.
- Always provide sRGB fallbacks when using wide-gamut (Display P3) or OKLCH colors.
- Generate harmonious palettes with hue rotation: complementary (+180°), triadic (+120°), analogous (±30°).