HEX vs RGB vs HSL: Understanding Color Formats in CSS
HEX, RGB, and HSL: when to use each color format in CSS. Understand how they work, their strengths, and convert between them with our free color converter.
Three Ways to Say the Same Color
The color teal can be expressed in CSS in all three formats:
/* HEX */
.teal-hex { color: #008080; }
/* RGB */
.teal-rgb { color: rgb(0, 128, 128); }
/* HSL */
.teal-hsl { color: hsl(180, 100%, 25%); }
All three produce the same color on screen. They differ in how you arrive at the values and when each format is easier to reason about. Understanding the trade-offs helps you pick the right one for the task.
HEX
Hexadecimal colors are the most compact format. Six hex digits, optionally
preceded by #:
#RRGGBB
Each pair of digits represents red, green, and blue intensity from 00 (0,
none) to FF (255, full). #FF0000 is pure red. #000000 is black.
#FFFFFF is white.
Three-digit shorthand is valid when each pair shares the same digit:
#F00 equals #FF0000 (red), #0F0 equals #00FF00 (green),
#333 equals #333333 (dark gray).
An 8-digit variant includes alpha transparency. #FF000080 is red at 50%
opacity. This is useful in design tokens for semi-transparent variants:
:root {
--surface-overlay: #00000040; /* 25% black overlay */
--brand-ghost: #3B82F620; /* 12% brand tint */
--shadow-subtle: #0000001A; /* 10% black shadow */
}
HEX is the most common format in CSS because it is compact and designers
copy-paste it directly from Figma, Photoshop, and color pickers. The
downside: HEX values do not map intuitively to how humans think about
color. #008080 is teal, but you cannot tell that from the digits. Making
it "a bit lighter" requires mental hex math.
RGB
RGB uses decimal values (0 to 255) for red, green, and blue:
/* Legacy syntax */
.legacy { color: rgb(0, 128, 128); }
.legacy-a { color: rgba(0, 128, 128, 0.5); }
/* Modern syntax (space-separated, no commas) */
.modern { color: rgb(0 128 128); }
.modern-a { color: rgb(0 128 128 / 0.5); }
RGB is straightforward: pick how much red, green, and blue you want. It matches the native format of screens since each pixel is a combination of red, green, and blue sub-pixels. The downside: adjusting a color by eye is unintuitive. To make teal "lighter but less saturated," you adjust three numbers simultaneously without knowing which direction to go.
Where RGB shines is programmatic manipulation. In JavaScript, you can read and write individual color channels:
const el = document.querySelector('.card');
const style = getComputedStyle(el);
const bg = style.backgroundColor; // "rgb(0, 128, 128)"
// Parse individual channels
const [r, g, b] = bg.match(/\d+/g).map(Number);
// Increase red channel by 30 for a warmer variant
el.style.backgroundColor = `rgb(${r + 30}, ${g}, ${b})`;
This channel-level access is impossible with HEX strings unless you parse them first. CSS animations also work naturally with RGB:
@keyframes pulse-red {
0% { background-color: rgb(255, 50, 50); }
50% { background-color: rgb(255, 100, 100); }
100% { background-color: rgb(255, 50, 50); }
}
HSL
HSL describes colors the way humans perceive them:
hsl(hue, saturation%, lightness%)
- Hue: the base color on the color wheel, 0 to 360 degrees. 0 is red, 120 is green, 240 is blue, 360 wraps back to red.
- Saturation: how intense the color is. 0% is gray (no color). 100% is full color.
- Lightness: how bright. 0% is black. 50% is the pure color. 100% is white.
Teal is hsl(180, 100%, 25%). To make it lighter, increase lightness to
35%. To make it less saturated, decrease saturation to 70%. These are
single-value adjustments that match the words designers use.
HSL with alpha follows the same modern syntax:
/* Legacy */
.legacy { color: hsla(180, 100%, 25%, 0.5); }
/* Modern */
.modern { color: hsl(180 100% 25% / 0.5); }
When to Use Each
| Format | Use When | |---|---| | HEX | Quick copy-paste from design tools. Compact commit diffs. Familiar to every developer. | | RGB | You need channel-level access in JavaScript. Color animations that transition smoothly. Dynamic color generation. | | HSL | You design in code and adjust colors intuitively. Building systematic color scales. Dark mode overrides. |
Building a Design-System Palette with HSL
The killer feature of HSL: generate an entire color scale by varying one parameter. Here is a full blue palette where only lightness changes:
:root {
--blue-50: hsl(210, 80%, 95%);
--blue-100: hsl(210, 80%, 88%);
--blue-200: hsl(210, 80%, 78%);
--blue-300: hsl(210, 80%, 65%);
--blue-400: hsl(210, 80%, 55%);
--blue-500: hsl(210, 80%, 50%); /* base */
--blue-600: hsl(210, 80%, 42%);
--blue-700: hsl(210, 80%, 32%);
--blue-800: hsl(210, 80%, 22%);
--blue-900: hsl(210, 80%, 15%);
}
Swap 210 (blue) for 340 (pink), 45 (gold), or 160 (emerald) and
you have an instant palette for any brand color. This is how Tailwind CSS
and other utility-first frameworks build their color scales.
For accessible contrast, pair lightness values with at least 40 points of
separation. --blue-100 (88% lightness) on --blue-800 (22% lightness)
passes WCAG AA contrast requirements.
You can also create semantic tokens on top of the scale:
:root {
--color-text-primary: var(--blue-900);
--color-text-secondary: var(--blue-600);
--color-surface: var(--blue-50);
--color-border: var(--blue-200);
--color-accent: var(--blue-500);
}
HSL for Dark Mode
HSL separates hue from lightness, which makes dark mode overrides trivial. Instead of rewriting every color, shift only the lightness values:
:root {
--color-bg: hsl(210, 30%, 98%);
--color-text: hsl(210, 30%, 15%);
}
[data-theme="dark"] {
--color-bg: hsl(210, 30%, 12%);
--color-text: hsl(210, 30%, 90%);
}
The hue and saturation stay the same. Only the lightness flips. With HEX or RGB you would need to compute entirely new values for each token.
For more complex systems, decompose HSL into individual custom properties so you can override one dimension at a time:
:root {
--hue: 210;
--saturation: 80%;
--color-primary: hsl(var(--hue), var(--saturation), 50%);
--color-primary-light: hsl(var(--hue), var(--saturation), 70%);
--color-primary-dark: hsl(var(--hue), var(--saturation), 30%);
}
[data-theme="dark"] {
--color-primary: hsl(var(--hue), 60%, 65%);
--color-primary-light: hsl(var(--hue), 60%, 80%);
--color-primary-dark: hsl(var(--hue), 60%, 40%);
}
This pattern (sometimes called "HSLu" for HSL with unbundled values) powers several popular component libraries and design systems.
Convert Between Formats
The Color Converter converts between HEX, RGB, and HSL in real time. Enter a HEX code and see the RGB and HSL equivalents. Adjust the HSL sliders and the HEX and RGB update instantly. This is the fastest way to understand how the same color looks in each format.
Try it yourself: open the Color Converter. Enter
#008080in the HEX field. Observe the RGB (0, 128, 128) and HSL (180, 100%, 25%) values. Use the HSL lightness slider to increase lightness to 50%. The color becomes a brighter teal, and the HEX and RGB update automatically. Try adjusting the saturation slider to see how muted the color becomes. Then paste a color from your own project and explore how it breaks down across all three formats.