GHGetHubApps

Colour on the web: formats, contrast and getting it right in dark mode

Design8 min readPublished

Colour on screen involves a handful of notations that describe the same thing differently, and choosing the right one for the job makes a surprising amount of work easier. There's also a set of genuinely counter-intuitive behaviours — gradients that go muddy, palettes that fall apart in dark mode, colours that look fine and fail accessibility — that all trace back to the same underlying cause.

This guide covers the formats, the accessibility thresholds that carry legal weight in many jurisdictions, and why perceptual colour spaces have started replacing the older ones.

The formats and what each is good for

FormatExampleReach for it when
HEX#4F46E5Pasting between design tools and code
RGBrgb(79 70 229)Manipulating channels programmatically
HSLhsl(243 75% 59%)Lightening, darkening or desaturating
OKLCHoklch(51% 0.24 277)Building a palette that looks even across hues
CMYK65 69 0 10Print only — never for screen

Hex and RGB describe the same sRGB value in different notation. HSL and OKLCH restructure it into dimensions humans can reason about.

Why HSL is easier to work in

To darken #4F46E5 in hex you'd adjust three channels by eye and hope the hue doesn't drift. In HSL you reduce the lightness value and nothing else changes. That's the whole reason design systems generate their colour ramps in HSL rather than hex — you get a consistent set of tints and shades from one base by varying a single number.

The catch is that HSL's lightness is not perceptual. hsl(60 100% 50%) is yellow and hsl(240 100% 50%) is blue; both claim 50% lightness, and the yellow is dramatically brighter to the eye. A palette built by holding L constant across hues therefore looks uneven — some swatches glare, others recede — and no amount of tweaking within HSL fully fixes it.

The muddy gradient problem

Browsers interpolate between colour stops in sRGB by default, and a straight line through sRGB between two saturated hues passes through a desaturated region. Blue to yellow is the standard demonstration: instead of passing through green, the midpoint goes grey.

This is the same underlying issue as the uneven palette — sRGB's numbers don't correspond to perceived colour in a uniform way. There are two practical fixes. Add an intermediate stop in the hue you want the gradient to travel through, or interpolate in a perceptual space: modern CSS supports linear-gradient(in oklch, blue, yellow), where the midpoint stays vivid without any manual stop.

Contrast requirements, concretely

Contrast ratio runs from 1:1 (identical) to 21:1 (pure black on pure white). WCAG sets thresholds that are cited in accessibility legislation in many jurisdictions, so these are frequently a legal requirement rather than a guideline.

ElementWCAG AAWCAG AAA
Body text4.5:17:1
Large text (18pt, or 14pt bold)3:14.5:1
UI components, borders, icons3:13:1
Decorative elements, logosNo requirementNo requirement

Contrast is symmetrical — swapping foreground and background gives the same ratio. Passing the threshold isn't the whole story either; very thin or very small type can still be hard to read at a technically compliant ratio.

Never rely on colour alone

  • Around 8% of men and 0.5% of women have some form of colour vision deficiency. Red-green confusion is by far the most common form.
  • A red-and-green status indicator with no other difference is invisible to a substantial number of users. Add an icon, a label, or a shape.
  • Form validation that only turns a border red fails the same way. Pair it with text explaining the problem.
  • Charts should differentiate series by more than hue — direct labels, varied dash patterns, or distinct markers.
  • Links inside body text need more than colour to be identifiable, which is why underlining them remains good practice.

Dark mode is not an inversion

A palette that works on white rarely works on near-black by simply flipping the values. Saturated colours that sit comfortably against white tend to vibrate unpleasantly against dark backgrounds, an effect worsened by the halation many people experience with light text on dark.

Two practical adjustments handle most of it. Desaturate and slightly lighten your accent colours for dark mode rather than reusing the same hex values. And avoid pure black backgrounds — a very dark grey around #121212 reduces the harshness of the contrast against white text while remaining unambiguously dark. Define your colours as tokens once and redefine only those tokens per theme; hard-coding a colour inside a dark-mode block is how themes drift out of sync.

Frequently asked questions

What contrast ratio do I need for body text?
4.5:1 under WCAG AA, or 7:1 for AAA. Large text — 18pt, or 14pt bold — needs 3:1 for AA. Interface components like borders and icons need 3:1.
Why does my gradient look grey in the middle?
Default interpolation happens in sRGB, and the straight path between two saturated hues passes through a desaturated zone. Add an intermediate stop, or interpolate in OKLCH with linear-gradient(in oklch, …).
What does the 8-digit hex format mean?
The last two digits are the alpha channel. #4F46E5FF is fully opaque and #4F46E580 is about 50% transparent.
Should I use OKLCH instead of hex?
For generating systematic palettes, yes — it produces even results across hues in a way hex and HSL can't. For storing and pasting individual brand values, hex remains perfectly fine and universally understood.
Can I trust an on-screen CMYK preview for print?
No. Accurate conversion depends on the specific paper, ink and press, so treat any generic CMYK conversion as an approximation and ask your printer for their ICC profile.

Tools for this

More guides