HEX, RGB & HSL Explained: A Practical Guide to Web Color Codes
A color on a screen can be written as #4F46E5, rgb(79, 70, 229), or hsl(243, 75%, 59%) — all the same purple. Here is what each format means and when to use which.
If you have ever poked at a website's styling, you have met color codes: cryptic strings like #4F46E5, rgb(79, 70, 229), or hsl(243, 75%, 59%). The confusing part is that all three of those describe the exact same shade of indigo — they are just three different ways of writing one color. Understanding what each format actually encodes makes picking, tweaking, and converting colors far less mysterious.
What a color code really is
Screens build every color from three colored lights: red, green, and blue (RGB). Mix them at different intensities and you get the roughly 16.7 million colors a standard display can show. Every color format is ultimately just a way of specifying "how much red, how much green, how much blue" — they differ only in notation and in how intuitive they are for humans to adjust.
HEX: the web's default shorthand
A HEX code is six hexadecimal digits, usually written with a leading hash, like #4F46E5. The digits come in three pairs: the first pair is the red value, the second green, the third blue — each ranging from 00 (none) to FF (maximum), which is 0 to 255 in everyday decimal. So #FF0000 is pure red, #000000 is black, and #FFFFFF is white.
HEX is compact and universally supported, which is why it is the format you see most in design tools and CSS. There is also a three-digit shorthand (#ABC expands to #AABBCC) and an eight-digit form that adds transparency. The downside: HEX is hard to adjust by eye. Looking at #4F46E5, it is not obvious how to make it "a bit lighter" without a tool.
RGB and RGBA: the literal version
RGB writes the same three numbers in plain decimal: rgb(79, 70, 229). It is functionally identical to HEX — just easier to read as actual values. RGBA adds a fourth number, the alpha channel, controlling opacity from 0 (fully transparent) to 1 (fully opaque): rgba(79, 70, 229, 0.5) is that indigo at 50% transparency.
RGB is handy when you are working programmatically — for example, generating colors in code or interpolating between two of them — because you can do arithmetic on the channels directly.
HSL: the format designers actually think in
HSL describes a color as Hue, Saturation, and Lightness: hsl(243, 75%, 59%). Hue is an angle from 0 to 360 degrees around a color wheel (0 is red, 120 is green, 240 is blue). Saturation is how vivid the color is, from 0% (grey) to 100% (full intensity). Lightness runs from 0% (black) through the pure color at 50% to 100% (white).
The reason designers love HSL is that it maps to how we actually reason about color. Want a darker version of a button for its hover state? Drop the lightness a few percent and leave hue and saturation alone. Want a muted, pastel variant? Lower the saturation. Building a palette of related colors? Keep the hue fixed and vary saturation and lightness. None of that is intuitive in HEX or RGB.
HSL has one significant flaw, and it catches people out when building palettes. Its "lightness" is a mathematical construct, not a measurement of how bright a color actually looks to a human eye. Set yellow and blue to the same HSL lightness — hsl(60, 100%, 50%) and hsl(240, 100%, 50%) — and the yellow will appear dramatically brighter, because our eyes are far more sensitive to green and yellow wavelengths than to blue. So a palette built by holding lightness constant across hues will not look evenly weighted. It will look like some colors are shouting.
The newer formats: OKLCH and why they exist
That flaw is precisely what the newer CSS color formats were designed to fix. Modern browsers support oklch(), which describes a color as Lightness, Chroma and Hue — structurally similar to HSL, but built on a color space engineered so that equal numerical steps correspond to equal perceived steps.
The practical consequence is that in OKLCH, holding lightness constant across hues genuinely produces colors of equal apparent brightness. Palettes generated by varying one number stay balanced instead of needing hand-correction. If you have ever built a set of status colors and found the yellow needed manual darkening to sit alongside the blue, this is the problem OKLCH removes.
It also reaches colors that HEX and RGB simply cannot express. Those formats are limited to the sRGB gamut, the color range of a typical monitor from decades ago. Modern displays cover considerably more — the Display P3 gamut in particular — and CSS can now address it directly through oklch() and the color() function. Those vivid greens and reds on a recent phone screen are not achievable in a six-digit HEX code.
For most work today, HEX remains the sensible default for storing brand colors, since every tool understands it. OKLCH is worth reaching for when you are generating palettes systematically or building a design system with programmatic tints and shades.
Contrast: the part that is not optional
Choosing colors that look good is a matter of taste. Choosing colors people can actually read is a matter of accessibility, and there are specific numbers attached to it.
The Web Content Accessibility Guidelines define contrast as a ratio between the relative luminance of foreground and background, running from 1:1 (identical) to 21:1 (pure black on pure white). To meet the widely used AA standard, normal body text needs a contrast ratio of at least 4.5:1 against its background. Large text — roughly 24px, or 19px if bold — needs 3:1. Interface components and meaningful graphics also need 3:1. The stricter AAA level raises body text to 7:1.
The trap is that contrast has almost nothing to do with hue and almost everything to do with luminance. Two colors can be vividly different — a saturated red on a saturated green — and still fail badly, because they are similarly bright. That combination is also close to invisible for the most common form of color blindness, which affects roughly one man in twelve. This is the origin of the broader rule that color must never be the only thing carrying information: pair it with a label, an icon, or a shape, so that "the red one" is not the only way to identify an error.
Light grey placeholder text on white is the most common real-world failure, and it is usually introduced by a designer working on a bright, high-quality monitor. On a phone in sunlight it disappears entirely.
Which one should you use?
Use HEX for storing and sharing fixed brand colors — it is compact and what most tools expect. Use RGB or RGBA when you need transparency or when manipulating colors in code. Use HSL when you are designing and want to create tints, shades, and harmonious palettes by hand. Use OKLCH when you are generating a systematic palette and need perceptual consistency, or when you want colors beyond the sRGB range. The good news is that converting between the classic three loses nothing — they all describe the same underlying color.
A note on dark mode, since it is where naive conversions fall apart. Inverting a light theme by flipping lightness values produces something harsh and wrong almost every time. Pure white text on pure black causes an effect called halation, where the text appears to smear for many readers, and saturated colors that looked confident on white become garish on black. The usual fixes are to use a very dark grey rather than true black, a slightly off-white rather than pure white, and to reduce saturation as backgrounds get darker. Dark mode is a separate palette, not a transformation of the light one.
Building a palette that holds together
A practical method: choose one brand hue, then generate a scale from it by varying lightness in even steps — typically nine or ten stops from very light to very dark. That single scale covers backgrounds, borders, text and hover states without ever introducing an arbitrary new color. Repeat for a neutral grey and for the handful of semantic colors you need (success, warning, error, information).
Two rules keep it disciplined. First, decide contrast pairings up front — which step of the scale is legible on which other step — and reuse those pairings everywhere rather than checking ad hoc. Second, resist adding colors. Most interfaces that feel chaotic are not badly colored; they simply contain thirty colors where eight would do.
If you need to move a value from one format to another, QTNest's Color Converter keeps HEX, RGB, and HSL in sync as you edit any of them, with a live preview swatch and one-click copy — so you can design in HSL and export the HEX your stylesheet needs.