Color Code Converter: HEX to RGB, HSL & More Online
Here is a number that should be uncomfortable: according to the WebAIM Million 2025 report, which analyzed 1,000,000 home pages, 79.1% of websites have low-contrast text that fails WCAG 2.1 AA thresholds. That is not a niche accessibility problem — it is the single most common failure on the web, averaging 29.6 violations per page. A large part of the cause is not malice but tooling: developers and designers pick colors visually and never convert them to check the actual contrast ratio.
Color code conversion — between HEX, RGB, HSL, and OKLCH — is the foundation of working with color correctly in CSS and design systems. This guide covers the conversion math, explains when each format is the right choice, and gives you working JavaScript implementations you can drop into any project.
If you need to convert a color right now, use the BytePane Color Converter — it converts between HEX, RGB, HSL, and OKLCH with live preview entirely in your browser.
Key Takeaways
- ▸HEX and RGB represent the same sRGB color space — HEX is just base-16 encoded RGB. Converting between them is arithmetic, not lossy.
- ▸HSL maps more closely to human color intuition but has a fatal flaw: its lightness value is not perceptually uniform across hues.
- ▸OKLCH solves HSL's perceptual problem, supports P3 wide-gamut colors, and has 92.93% browser support (Feb 2026). Tailwind CSS v4 defaulted to it in January 2025.
- ▸Per the HTTP Archive Web Almanac 2022, HEX is still the dominant format at ~74% of web color declarations. HSL is used on fewer than 1% of pages.
- ▸79.1% of websites fail color accessibility (WebAIM 2025). Use the WCAG contrast ratio formula when converting colors for text use.
HEX Color Codes: The Dominant Web Format
HEX is the oldest and most widely used CSS color format. According to HTTP Archive Web Almanac 2022 CSS data, 6-digit HEX accounts for 49% of all color declarations and 3-digit shorthand for another 25% — collectively, HEX handles approximately 74% of all web color usage. It dominated at 93% as recently as 2019, and despite years of HSL advocacy, it remains engineers' default choice.
The format is simple: #RRGGBB where each pair of characters is a hex byte (00–FF) encoding one color channel. The alpha channel is appended as a fourth pair in 8-digit notation: #FF573380 (50% opacity). HEX is case-insensitive; #ff5733 and #FF5733 are identical.
/* HEX color notation */
#FF5733 /* 6-digit: R=FF(255), G=57(87), B=33(51) */
#F53 /* 3-digit shorthand — only when pairs double: FF→F, 55→5, 33→3 */
#FF573380 /* 8-digit with alpha: 80 hex = 128 decimal = ~50% opacity */
/* Cannot shorten #FF5733 because the pairs are not doubled */
/* #F5734F cannot be shortened — each pair is unique */
/* Named colors map to HEX internally */
red /* = #FF0000 */
royalblue /* = #4169E1 */HEX → RGB Conversion: The Algorithm
HEX to RGB conversion is pure base arithmetic — no math beyond integer parsing. Each hex pair is parsed as a base-16 integer: FF = 255, 80 = 128, 00 = 0. This is a lossless, reversible operation.
// HEX to RGB — handles 3-digit and 6-digit inputs
function hexToRgb(hex: string): { r: number; g: number; b: number } | null {
// Expand 3-digit to 6-digit: #ABC → #AABBCC
const shorthand = /^#?([a-fd])([a-fd])([a-fd])$/i;
hex = hex.replace(shorthand, (_, r, g, b) => r + r + g + g + b + b);
const result = /^#?([a-fd]{2})([a-fd]{2})([a-fd]{2})$/i.exec(hex);
if (!result) return null;
return {
r: parseInt(result[1], 16), // FF → 255
g: parseInt(result[2], 16), // 57 → 87
b: parseInt(result[3], 16), // 33 → 51
};
}
// RGB to HEX
function rgbToHex(r: number, g: number, b: number): string {
return '#' + [r, g, b]
.map(v => Math.max(0, Math.min(255, Math.round(v))).toString(16).padStart(2, '0'))
.join('')
.toUpperCase();
}
// Examples
hexToRgb('#FF5733') // { r: 255, g: 87, b: 51 }
rgbToHex(255, 87, 51) // '#FF5733'RGB → HSL Conversion: The Full Math
RGB to HSL conversion is non-trivial. Unlike HEX↔RGB (which is just base conversion), this involves finding the dominant channel, computing a relative range, and then projecting into cylindrical color space. The algorithm follows the IEC 61966-2-1 sRGB specification.
// RGB to HSL — returns { h: 0-360, s: 0-100, l: 0-100 }
function rgbToHsl(r: number, g: number, b: number): { h: number; s: number; l: number } {
// Step 1: Normalize to 0–1 range
r /= 255; g /= 255; b /= 255;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
const delta = max - min;
// Step 2: Calculate Lightness
const l = (max + min) / 2;
// Step 3: Calculate Saturation
const s = delta === 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));
// Step 4: Calculate Hue (which channel is dominant?)
let h = 0;
if (delta !== 0) {
switch (max) {
case r: h = ((g - b) / delta) % 6; break;
case g: h = (b - r) / delta + 2; break;
case b: h = (r - g) / delta + 4; break;
}
h = Math.round(h * 60);
if (h < 0) h += 360;
}
return {
h,
s: Math.round(s * 100),
l: Math.round(l * 100),
};
}
// HSL to RGB — MDN-recommended algorithm
function hslToRgb(h: number, s: number, l: number): { r: number; g: number; b: number } {
s /= 100; l /= 100;
const a = s * Math.min(l, 1 - l);
const f = (n: number) => {
const k = (n + h / 30) % 12;
return l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
};
return {
r: Math.round(f(0) * 255),
g: Math.round(f(8) * 255),
b: Math.round(f(4) * 255),
};
}
// Example: #FF5733
// rgbToHsl(255, 87, 51) → { h: 11, s: 100, l: 60 }
// Meaning: warm orange-red, fully saturated, 60% lightnessThe HSL model is intuitive: hue rotates around a color wheel (0°=red, 120°=green, 240°=blue), saturation controls purity, and lightness controls brightness. It is why CSS custom properties for theming often use HSL — generating a hover state is just hsl(from var(--color) h s calc(l - 10%)).
But HSL has a serious flaw that matters for accessibility: its lightness value is perceptually non-uniform. Yellow at hsl(60 100% 50%) appears dramatically brighter than blue at hsl(240 100% 50%) despite identical L values. This makes algorithmic palette generation unreliable — which is why OKLCH was developed.
For more depth on the full color format landscape, see our guide to color formats: HEX, RGB, HSL & beyond.
OKLCH: The Format That Gets Lightness Right
OKLCH (Lightness, Chroma, Hue in the Oklab color space) solves HSL's perceptual uniformity problem. It was designed by Björn Ottosson in 2020 specifically to address the hue-lightness inconsistency in earlier CIE Lab-based spaces like LCH. The “OK” prefix stands for Ottosson's corrected Oklab space.
The critical adoption signal: Tailwind CSS v4, released January 2025, migrated its entire 242-color default palette to OKLCH. For the Tailwind team, the primary motivation was supporting P3 wide-gamut colors on modern displays — sRGB (which HEX/RGB/HSL are limited to) covers only about 35% of human-visible colors, while P3 adds roughly 30% more, visible on all iPhones since 2016 and most modern OLED displays.
/* OKLCH syntax */
/* oklch(L C H) — L: 0–1, C: 0–0.4+, H: 0–360 */
.brand-color {
color: oklch(0.65 0.18 27); /* orange-red, ~equivalent to #FF5733 */
}
/* The power: manipulating with CSS relative color syntax */
.brand-hover {
/* Darken by reducing lightness — perceptually consistent across ALL hues */
color: oklch(from var(--brand) calc(l - 0.1) c h);
}
/* Generate a full color scale algorithmically */
:root {
--base-hue: 27;
--color-100: oklch(0.95 0.04 var(--base-hue));
--color-500: oklch(0.65 0.18 var(--base-hue));
--color-900: oklch(0.25 0.10 var(--base-hue));
/* Each step is perceptually equidistant — unlike HSL */
}
/* Wide-gamut P3 color (impossible in HEX/RGB/HSL) */
.vivid-green {
color: oklch(0.85 0.30 145); /* Beyond sRGB gamut */
}
/* Feature detection fallback */
@supports not (color: oklch(0 0 0)) {
.vivid-green {
color: #4CAF50; /* sRGB fallback */
}
}Converting RGB to OKLCH requires a chain of matrix transformations: sRGB → Linear RGB (gamma removal) → XYZ-D65 → Oklab (via two 3×3 matrices) → OKLCH (polar coordinates). It is too computationally involved for manual calculation — use the Color Converter tool or the colorjs library (the reference implementation).
Format Comparison: HEX vs RGB vs HSL vs OKLCH
| Property | HEX | RGB | HSL | OKLCH |
|---|---|---|---|---|
| Color gamut | sRGB only | sRGB only | sRGB only | P3, Rec2020+ |
| Perceptual uniformity | No | No | Partial | Yes |
| Human-readable | No | Partial | Yes | Yes |
| Browser support | ~100% | ~100% | ~100% | 92.93% |
| Copy-paste utility | Excellent | Good | Good | Moderate |
| Gradient quality | Dead zones | Dead zones | Varies | Smooth/natural |
| Figma native support | Yes | Yes | Yes | Plugin only |
| Web usage (HTTP Archive 2022) | ~74% | ~15% | <1% | Growing |
| Tailwind v4 default | No | No | No | Yes (2025) |
The practical recommendation: use HEX for copy-pasting from design tools, RGB when you need programmatic alpha channels in older codebases, HSL for legacy CSS variable systems, and OKLCH for all new CSS and design tokens in 2026. The Figma OKLCH gap is real but closing — plugins like OKLCH Picker provide workflow parity.
CSS Color Level 4: New Features That Changed the Game
The W3C CSS Color Module Level 4 (Candidate Recommendation Draft, March 2026) introduced three features that make color conversion and manipulation dramatically more powerful directly in CSS.
color-mix(): Mixing Colors in Any Color Space
/* color-mix() — Baseline 2023, all modern browsers */
.example {
/* Mix red and blue, 30% red, 70% blue, computed in oklch space */
background: color-mix(in oklch, red 30%, blue);
/* The color space matters — different spaces produce different results */
/* in srgb: produces grey dead zone for opposite hues */
/* in oklch: smooth, perceptually uniform mix */
background: color-mix(in oklch, #FF0000, #0000FF); /* vivid purple */
background: color-mix(in srgb, #FF0000, #0000FF); /* muddy grey */
}
/* Useful for hover states without JavaScript */
.button {
--color: oklch(0.65 0.18 27);
background: var(--color);
}
.button:hover {
background: color-mix(in oklch, var(--color) 80%, black);
}Relative Color Syntax: Modifying Existing Colors
/* Relative color syntax — Baseline 2024 (Chrome 119+, Firefox 128+, Safari 16.4+) */
:root {
--brand: oklch(0.65 0.18 27);
}
.lighter {
/* Take --brand, keep its chroma and hue, add 0.15 to lightness */
color: oklch(from var(--brand) calc(l + 0.15) c h);
}
.desaturated {
/* Keep hue and lightness, halve the chroma */
color: oklch(from var(--brand) l calc(c * 0.5) h);
}
.complementary {
/* Rotate hue by 180° — creates the complementary color */
color: oklch(from var(--brand) l c calc(h + 180));
}
/* This replaces dozens of CSS variables with one source of truth */light-dark(): Theme-Aware Color Values
/* light-dark() — Chrome 123+, Firefox 120+, Safari 17.5+ */
:root {
color-scheme: light dark;
}
.card {
background: light-dark(
oklch(0.97 0.02 27), /* light mode: very light orange */
oklch(0.20 0.04 27) /* dark mode: very dark orange */
);
color: light-dark(
oklch(0.20 0.05 27),
oklch(0.90 0.05 27)
);
}These three features together eliminate most JavaScript-based color manipulation from design systems. Converting a brand color to a full accessible palette — hover states, disabled states, dark mode variants — is now achievable in pure CSS.
For hands-on color exploration, the BytePane Color Picker supports OKLCH and shows the contrast ratio against common background colors in real time.
Color Conversion and Accessibility: The WCAG Contrast Ratio
Converting colors is not just a visual workflow — it is a prerequisite for accessibility compliance. WCAG 2.1 requires a 4.5:1 contrast ratio for normal text (Level AA) and 7:1 for enhanced (Level AAA). Large text (24px+ or 18px+ bold) has a lower minimum of 3:1.
The contrast ratio is computed from relative luminance — a value derived from linearized RGB, not from HSL lightness or OKLCH L. This is why you cannot eyeball accessibility: two colors that look fine on a calibrated monitor may fail at 3.2:1 in the math. The WebAIM Million 2025 report found 94.8% of all tested home pages had at least one detectable WCAG failure, with low contrast text appearing on 79.1% of all pages.
// WCAG 2.1 contrast ratio calculation
// Input: two RGB colors
function getRelativeLuminance(r: number, g: number, b: number): number {
// Step 1: Normalize to 0–1
const [rs, gs, bs] = [r, g, b].map(c => c / 255);
// Step 2: Linearize (remove gamma)
const linearize = (c: number) =>
c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
const [rl, gl, bl] = [rs, gs, bs].map(linearize);
// Step 3: Compute luminance per WCAG formula
return 0.2126 * rl + 0.7152 * gl + 0.0722 * bl;
}
function getContrastRatio(hex1: string, hex2: string): number {
const { r: r1, g: g1, b: b1 } = hexToRgb(hex1)!;
const { r: r2, g: g2, b: b2 } = hexToRgb(hex2)!;
const l1 = getRelativeLuminance(r1, g1, b1);
const l2 = getRelativeLuminance(r2, g2, b2);
const lighter = Math.max(l1, l2);
const darker = Math.min(l1, l2);
return (lighter + 0.05) / (darker + 0.05);
}
// Examples
getContrastRatio('#FFFFFF', '#000000') // 21:1 — maximum contrast
getContrastRatio('#FFFFFF', '#767676') // 4.54:1 — passes AA normal text
getContrastRatio('#FFFFFF', '#949494') // 2.85:1 — fails AA normal textNotice that the luminance formula uses channel weights (R: 21.26%, G: 71.52%, B: 7.22%) because human eyes are most sensitive to green. This is why yellow text on white is almost always inaccessible despite looking bright — the green and red channels both contribute high luminance, and white contributes even higher, leaving almost no contrast ratio.
Complete Color Conversion Reference: All Format Pairs
The table below shows the conversion chain between all four major CSS color formats. Direct conversions exist for HEX↔RGB. All other conversions route through RGB as an intermediate step.
| From → To | Direct? | Intermediate Steps | Lossless? |
|---|---|---|---|
| HEX → RGB | Yes | parseInt(hex, 16) | Yes |
| RGB → HEX | Yes | n.toString(16) | Yes |
| HEX → HSL | No | HEX → RGB → HSL | Near-lossless* |
| HSL → HEX | No | HSL → RGB → HEX | Near-lossless* |
| RGB → OKLCH | No | sRGB → Linear → XYZ-D65 → Oklab → OKLCH | Yes (within sRGB) |
| OKLCH → HEX | No | OKLCH → Oklab → XYZ → Linear → sRGB → HEX | May clip P3 colors |
* Near-lossless: floating-point rounding during HSL conversion may shift RGB values by ±1. Round-tripping HEX→HSL→HEX may not produce identical HEX output.
When to Use Each Color Format
Use HEX when:
- •Copying colors from Figma, Sketch, or any design tool (they all export HEX)
- •Writing utility CSS or Tailwind classes where brevity matters
- •Setting colors in SVG attributes or HTML inline styles
- •Legacy codebases where consistency with existing code outweighs other factors
Use HSL when:
- •Building a CSS variable-based design token system that will be modified by non-OKLCH-familiar designers
- •Working in a codebase that predates CSS Color Level 4 and has established HSL conventions
- •Need 100% browser compatibility including older mobile browsers
Use OKLCH for all new projects in 2026:
- •New design systems and CSS custom property sets (92.93% browser support)
- •Gradient generation (no grey dead zones)
- •Palette generation from a single seed color (consistent perceived lightness)
- •Any project targeting modern displays (iPhone, OLED) where P3 wide-gamut is a feature
- •Using Tailwind CSS v4 (it defaults to OKLCH)
Related reading: CSS Flexbox vs Grid for layout decisions, and see the Web Accessibility Testing Guide for integrating contrast checking into your CI/CD pipeline.
Frequently Asked Questions
How do I convert HEX to RGB?
Split the HEX code into three two-character pairs and convert each from base-16 to decimal. For #FF5733: FF = 255, 57 = 87, 33 = 51, giving rgb(255, 87, 51). In JavaScript, parseInt("FF", 16) returns 255. For 3-digit HEX, expand first: #F53 → #FF5533.
What is the difference between RGB and HSL?
RGB and HSL encode the same sRGB colors differently. RGB defines colors by mixing light channels (red, green, blue, 0–255 each). HSL uses hue angle (0–360°), saturation percentage, and lightness percentage. HSL is more intuitive for manipulation — darkening means reducing lightness. They represent identical color ranges; no colors are accessible in one but not the other.
Should I use HSL or OKLCH in CSS in 2026?
OKLCH for new projects. It has 92.93% browser support as of early 2026 and Tailwind CSS v4 adopted it as default in January 2025. Its critical advantage over HSL: equal L values produce equal perceived brightness across all hues, making palette and hover state generation reliable. HSL's lightness is mathematically defined but perceptually inconsistent — yellow at 50% lightness looks far brighter than blue at 50% lightness.
Why do 79.1% of websites fail color accessibility checks?
According to the WebAIM Million 2025 report, low contrast text is the most common accessibility failure, averaging 29.6 violations per page. The root cause: designers select colors visually on calibrated monitors in bright offices, without checking the WCAG contrast ratio formula. Colors that look fine at high brightness often fail the 4.5:1 AA threshold when rendered under different conditions.
What does the # symbol mean in a HEX color code?
The # prefix identifies a hexadecimal color value in CSS. The six characters following it encode three color channels (red, green, blue) as two hexadecimal digits each (00–FF, equivalent to 0–255 decimal). Some contexts accept HEX without the prefix (certain SVG attributes, some design tools), but in CSS it is required syntax.
Can I use OKLCH in all modern browsers?
Yes. Chrome/Edge 111+ (March 2023), Firefox 113+ (May 2023), and Safari 15.4+ (March 2022) all support oklch(). Global support is 92.93% as of February 2026. For the ~7% remainder, add a HEX or HSL fallback above the OKLCH declaration. CSS ignores properties it does not understand, so the fallback pattern works without @supports.
What is the difference between HSL and OKLCH lightness?
HSL lightness is a mathematical average of the RGB channels — not calibrated to human perception. Yellow at hsl(60 100% 50%) looks far brighter than blue at hsl(240 100% 50%), despite identical L values. OKLCH derives L from the Oklab model, which is calibrated against psychophysical data. Equal OKLCH L values produce equal perceived brightness across all hues.
Convert Color Codes Instantly
Use the BytePane Color Converter to convert between HEX, RGB, HSL, and OKLCH with live preview — no sign-up, no tracking, runs entirely in your browser.
Open Color Converter →