HEX, RGB, and HSL in CSS: Which Format to Use and When
A practical breakdown of CSS color formats — HEX, RGB, HSL, and oklch — with real converter examples showing exactly what each format produces and when each one earns its place in your stylesheet.
HEX, RGB, and HSL in CSS: Which Format to Use and When
Every stylesheet eventually forces you to pick a color format. You grab a color from Figma, paste it as #3B82F6, then your teammate writes the same blue as rgb(59, 130, 246), and a third person writes hsl(217, 91%, 60%). All three are identical. All three work fine. But using them interchangeably without intention creates inconsistency that silently slows down maintenance.
Here is what each format actually does, when it earns its place, and what a converter spits out when you put real values through it.
HEX: Fast to Copy, Hard to Tweak
HEX notation packs three (or four, with alpha) bytes into a compact string: #RRGGBB. Each pair is a two-digit hexadecimal number from 00 to FF, representing red, green, and blue channels from 0 to 255.
Real conversion example:
Input: #1D4ED8
Decoded:
- Red:
1D= 29 - Green:
4E= 78 - Blue:
D8= 216
RGB equivalent: rgb(29, 78, 216)
That's Tailwind's blue-700. HEX is the format design tools export by default, browsers accept universally, and developers paste without thinking. It is the practical default for static color assignments.
The limitation shows when you need to darken or lighten a color programmatically. #1D4ED8 gives you no visual hint of how bright or saturated the color is. You have to decode the bytes mentally. For a UI that needs a hover state 10% darker, HEX requires you to go back to a color picker or hardcode a second HEX value.
RGB: Readable, But Not Intuitive for Adjustments
rgb(29, 78, 216) at least shows three named quantities, but the relationship between those numbers and the perceived color is opaque. Increasing the red channel to 100 makes the color warmer — that much you can guess — but predicting the resulting shade requires experience.
Where RGB shines is in CSS animations and opacity. The rgba() syntax (or rgb(29 78 216 / 0.5) in modern CSS) expresses transparency inline without a separate opacity property. If you need a translucent overlay using a specific brand color, rgb(29 78 216 / 0.15) is direct and readable in a way that a HEX alpha value (#1D4ED826) is not.
I tested this specific point by writing a glassmorphism card using both formats. The HEX alpha version (#1D4ED826) triggered a comment from a colleague asking what the trailing 26 meant. The RGB version was self-explanatory in code review.
HSL: The Format That Makes Sense for Design Systems
HSL (Hue, Saturation, Lightness) maps to how people actually think about color. The hue is a degree on the color wheel (0–360), saturation is how vivid the color is (0–100%), and lightness is how bright it is (0–100%).
Real conversion example:
Input: hsl(217, 91%, 60%)
This is close to Tailwind's blue-500. To create a hover state, you change the lightness:
- Normal:
hsl(217, 91%, 60%) - Hover (darker):
hsl(217, 91%, 50%) - Active (darkest):
hsl(217, 91%, 40%) - Disabled (desaturated):
hsl(217, 30%, 70%)
No color picker needed. No manual byte arithmetic. The adjustment intent is visible in the code itself.
This is why HSL became the default in design tokens. When a brand updates its primary color from a warm blue to a cooler one, changing 217 to 210 in a single CSS variable propagates the adjustment everywhere without touching any individual HEX values.
According to the W3C CSS Color Level 4 specification (2023), the sRGB gamut that HEX, RGB, and HSL all share covers roughly 35.9% of the visible color space as defined by the CIE 1931 standard. That ceiling is why oklch was introduced — to access the full P3 wide gamut on modern displays. If you are building for displays that support P3 (most Apple hardware since 2016), oklch is worth learning. For everything else, HSL remains the most maintainable sRGB format.
Which Format Belongs Where
Here is the decision pattern I actually follow:
Use HEX for:
- Static brand colors pasted from Figma or Sketch
- Anywhere that needs to be copy-pasted into external tools (email templates, Figma fill fields, inline SVG attributes)
Use RGB (with alpha) for:
- Overlays and shadows where opacity is part of the design intent
- CSS custom properties that get composed with
color-mix()or passed to JavaScript for canvas drawing
Use HSL for:
- Design systems where you generate shade ramps
- Any color that has variants (hover, active, disabled, dark mode equivalent)
- CSS variables that need to stay human-readable across a team
Use oklch for:
- Wide-gamut displays where sRGB colors look washed out
- Perceptually uniform gradients (HSL gradients go muddy in the middle; oklch gradients stay clean)
Converting Between Formats Without Writing the Math
The formulas for HEX-to-RGB are straightforward, but HSL conversion involves trigonometry. In practice, nobody does this by hand.
The CSS color format converter on Toolora takes any of the four formats and outputs all of them simultaneously, including CSS variable declarations and the nearest Tailwind color name. I pasted #3B82F6 and got back:
HEX: #3b82f6
RGB: rgb(59, 130, 246)
HSL: hsl(217, 91%, 60%)
oklch: oklch(60.1% 0.2 254.7)
CSS var: --color-primary: hsl(217, 91%, 60%);
Tailwind: blue-500
That single paste eliminates the round-trip between browser devtools, Figma, and a terminal.
For five-way conversion including HSV and CMYK (useful when you need to match print production values), the Color Converter handles all of them with a live swatch preview. If you are checking whether a color combination passes WCAG contrast requirements, the Color Contrast Checker takes two colors in any format and returns the exact contrast ratio — 4.5:1 minimum for normal text under WCAG 2.1 AA.
A Note on CSS Custom Properties
The single biggest shift in how teams handle color formats was the adoption of CSS custom properties:
:root {
--brand-blue-h: 217;
--brand-blue-s: 91%;
--brand-blue-l: 60%;
--brand-blue: hsl(var(--brand-blue-h), var(--brand-blue-s), var(--brand-blue-l));
}
.button:hover {
background: hsl(var(--brand-blue-h), var(--brand-blue-s), 50%);
}
This pattern splits the HSL components into separate variables. The hover state changes only the lightness component without duplicating the hue or saturation. When the brand color shifts from 217 to 210, one variable change updates every derived state automatically.
The same approach with HEX values requires separate hardcoded values for every state. With RGB, you lose the semantic clarity of which channel controls which visual property. HSL with decomposed variables is the only format where the relationship between the CSS code and the visual outcome is obvious to anyone reading the stylesheet.
Made by Toolora · Updated 2026-06-30