CSS Color Formats: HEX vs RGB vs HSL vs OKLCH
CSS gives you multiple ways to define colors, from the classic HEX codes to the modern OKLCH. Each format has different strengths β understanding them helps you pick the right one for your project, build consistent color systems, and avoid surprises.
HEX β The Classic
HEX colors represent red, green, and blue channels as hexadecimal values:
color: #3b82f6; /* 6-digit: RR GG BB */
color: #3b82f680; /* 8-digit: RR GG BB AA (with opacity) */
color: #38f; /* 3-digit shorthand: R G B */
Each pair of hex digits represents a value from 0 (00) to 255 (ff):
| Component | HEX | Decimal |
|---|---|---|
| Red | 3b | 59 |
| Green | 82 | 130 |
| Blue | f6 | 246 |
When to use HEX:
- Quick one-off colors
- Pasting values from Figma, Sketch, or design tools (they default to HEX)
- When you want compact notation
Drawbacks:
- Not human-readable β
#3b82f6doesnβt tell you the color is blue - Hard to create variations (lighter, darker, more muted) by editing values
- No built-in opacity (though 8-digit HEX adds it)
RGB β Red, Green, Blue
RGB defines colors using the additive color model β the same model your screen uses:
/* Modern syntax (recommended) */
color: rgb(59 130 246);
color: rgb(59 130 246 / 0.5); /* with 50% opacity */
/* Legacy syntax (still works) */
color: rgb(59, 130, 246);
color: rgba(59, 130, 246, 0.5);
Each channel is 0β255. You can also use percentages: rgb(23% 51% 96%).
When to use RGB:
- When working with canvas, WebGL, or image processing (they use RGB natively)
- When your color values come from an API or computation in RGB
- When you need programmatic color manipulation
Drawbacks:
- Not intuitive β βmake this blue slightly lighterβ requires changing multiple values
- Perceptually non-uniform β equal numeric changes donβt look like equal visual changes
HSL β Hue, Saturation, Lightness
HSL maps colors to a cylinder, making relationships between colors intuitive:
color: hsl(220 90% 56%);
color: hsl(220 90% 56% / 0.5); /* with opacity */
| Component | Range | Meaning |
|---|---|---|
| H (Hue) | 0β360 | Position on the color wheel (0=red, 120=green, 240=blue) |
| S (Saturation) | 0β100% | Intensity (0%=gray, 100%=vivid) |
| L (Lightness) | 0β100% | Brightness (0%=black, 50%=pure color, 100%=white) |
When to use HSL:
- Building color palettes and design systems
- Creating hover/active states (just change lightness)
- When you need color variations that are easy to reason about
:root {
--brand: hsl(220 90% 56%);
--brand-light: hsl(220 90% 70%); /* lighter: increase L */
--brand-dark: hsl(220 90% 40%); /* darker: decrease L */
--brand-muted: hsl(220 30% 56%); /* muted: decrease S */
}
Drawbacks:
- Perceptually non-uniform β this is the big one.
hsl(60 100% 50%)(yellow) looks far brighter thanhsl(240 100% 50%)(blue), even though both have the same lightness value. This makes it hard to create palettes where colors feel equally prominent.
OKLCH β The Modern Standard
OKLCH is a perceptually uniform color space introduced in CSS Color Level 4:
color: oklch(0.63 0.18 255);
color: oklch(0.63 0.18 255 / 0.5); /* with opacity */
| Component | Range | Meaning |
|---|---|---|
| L (Lightness) | 0β1 | Perceptual brightness (0=black, 1=white) |
| C (Chroma) | 0β0.4+ | Color intensity (0=gray, higher=more vivid) |
| H (Hue) | 0β360 | Color wheel position (similar to HSL) |
The key difference from HSL: lightness is perceptually uniform. Changing L by the same amount always looks like the same amount of change, regardless of hue. This makes it possible to create color palettes where all colors feel equally bright.
/* These all look equally bright β try this with HSL and they won't */
--red: oklch(0.65 0.2 25);
--green: oklch(0.65 0.2 145);
--blue: oklch(0.65 0.2 255);
--purple: oklch(0.65 0.2 310);
When to use OKLCH:
- Design systems and color tokens (consistent perceived brightness)
- Accessible color palettes (predictable contrast ratios)
- Any project where color consistency across hues matters
Browser support: Chrome 111+, Safari 16.4+, Firefox 113+ (all 2023+). For older browsers, add a fallback:
.button {
background: #3b82f6; /* fallback */
background: oklch(0.63 0.18 255); /* modern */
}
Side-by-Side Comparison
| HEX | RGB | HSL | OKLCH | |
|---|---|---|---|---|
| Readability | Low | Low | High | High |
| Creating variations | Hard | Hard | Easy | Easy |
| Perceptually uniform | No | No | No | Yes |
| Opacity support | 8-digit | / alpha | / alpha | / alpha |
| Browser support | All | All | All | Modern (2023+) |
| Best for | Quick values | Computation | Palettes | Design systems |
Named Colors
CSS also supports 148 named colors: red, cornflowerblue, rebeccapurple, etc. Theyβre great for prototyping and readable code, but limited for production use since you canβt adjust them.
color: cornflowerblue; /* #6495ED / hsl(219 79% 66%) */
color: rebeccapurple; /* #663399 β added as a tribute to Eric Meyer's daughter */
Practical Tips
Use CSS custom properties for your palette. Define colors once, reference everywhere:
:root {
--color-primary: oklch(0.63 0.18 255);
--color-primary-hover: oklch(0.56 0.18 255); /* darker */
--color-primary-light: oklch(0.90 0.05 255); /* light bg */
}
Use currentColor for derived values. It inherits the text color, reducing repetition:
.icon {
fill: currentColor;
border: 1px solid currentColor;
}
Test contrast ratios. WCAG 2.1 requires 4.5:1 contrast for normal text and 3:1 for large text. Use tools like our Color Converter to check values and convert between formats.
Try It Yourself
Use our Color Converter to convert between HEX, RGB, and HSL formats instantly β with a visual color picker and CSS-ready output values.
Further Reading
- CSS Color Level 4 Specification β W3C spec including OKLCH and OKLAB
- OKLCH Color Picker β Interactive OKLCH color picker by Evil Martians
- MDN: CSS color values β Complete reference for all CSS color formats
- WCAG Contrast Requirements β Accessibility guidelines for color contrast