Colour and units
Writing a colour three ways, measuring in units that scale, and the contrast rule that keeps text readable.
A colour is three numbers
Every colour you see is red, green and blue mixed in some amount. #059669 is hexadecimal — two digits each for red, green and blue — and rgb(5, 150, 105) is the same colour written in plain numbers. hsl(160, 94%, 30%) says it differently again: a hue, how saturated it is, how light.
The formats are interchangeable, so choose by what you are doing. Hex is compact and everywhere; rgb() makes an alpha channel easy — rgb(5 150 105 / 0.5) is the same green at half transparency; hsl() is the one to reach for when you want a lighter or darker version of a colour by changing one number.
One colour, three ways
.emerald { color: #059669; }
.emerald-soft { background: rgb(5 150 105 / 0.12); }
.emerald-dark { color: hsl(160 94% 22%); }Units that scale
px is a pixel — predictable, and blind to the reader’s settings. rem is the size of the page’s base text: 1.5rem is one and a half times the reader’s own font size, so it grows when they enlarge text and shrinks on a small screen. % and vw measure against a parent and the viewport.
The habit that keeps pages readable: use rem for type and spacing, % or ch for widths, and reach for px for things that should not scale, like a hairline border.
The rule behind the colour
Text has to be readable: 4.5 to 1 between the text colour and its background, at the sizes this page uses. Pale grey on white looks elegant in a mockup and vanishes on a phone in sunlight.
Contrast is measurable, and browsers will show you the ratio in their colour pickers. If a colour pair is close, darken the text rather than lightening the design — the person struggling to read it is not the one who will complain.
Contrast is a property of the pair, not of the colour. The same grey is readable on white and invisible on a mid-tone surface, so the ratio has to be checked against the background the text actually sits on — including a background image, where the safe answer is a solid layer behind the text. Large text is allowed a lower ratio, around 3 to 1, because the letterforms are bigger; small text gets no such allowance.
Reading a colour backwards
A hex value is six base-sixteen digits in three pairs: red, green, blue, each from 00 to ff. #059669 is 05 red, 96 green and 69 blue — very little red, a lot of green, a medium blue. The browser converts all three formats to the same internal numbers, which is why they can be swapped without a visible change.
HSL describes that same colour with different coordinates. Hue is an angle on the colour wheel, from 0 for red through 120 for green to 240 for blue; saturation is how far the colour is from grey; lightness runs from black at 0% to white at 100%. At 30% lightness the green is deep enough to read as text on white.
Which unit resolves against what
A relative unit only means something next to the thing it resolves against. rem looks at the root font size; em looks at the font size of the element itself, so nested em values multiply. A width in % looks at the parent’s content width, and vw looks at the viewport — including the strip a scrollbar occupies in some browsers.
That difference is the whole decision. rem keeps spacing and type tied to the reader’s setting. em is right when a component should scale as a unit, such as the padding inside a button whose label grows. vw is right for a full-bleed band and wrong for body text, where a narrow window can shrink the words below reading size.
Three colour and unit traps: % padding resolves against the element’s width even when it is padding-top, so vertical percentages surprise you; nested em sizes compound, so a 0.9em heading inside a 0.9em section is smaller than either rule suggests; and 100vw includes the scrollbar, so a full-width band can cause sideways scrolling. When a value is not the size you expected, ask what it is measuring against.
Try these next
- Build three steps of one hue with
hsl(): a pale surface, a mid-tone border and a dark text colour. - Set the body text in
remand a hairline rule inpx, then raise the browser’s default font size and compare. - Write one
padding-topin%and one inrem, and explain why only the first changes when the window narrows.
A small palette, written three ways
Give the heading a hex colour, the notice a see-through rgb background, and the page’s padding in rem. Three declarations, three formats.
3 left to do
- A hex colour is used
- An rgb() colour is used
- A rem measurement is used
color: #059669;, background: rgb(5 150 105 / 0.12);, padding: 2rem;.
One hue, three jobs
Build a card from a single hue: a pale surface, a mid-tone border and dark text, all written with `hsl()`. Measure the space inside in `rem`, and keep the edge at one pixel.
3 left to do
- The card colours are written with hsl()
- The card has a one-pixel edge
- The inside space is measured in rem
Keep the hue and saturation fixed and change only the lightness: hsl(160 40% 95%) for the surface, hsl(160 40% 70%) for the border and hsl(160 40% 20%) for the ink.
hsl() is the friendliest format for building a palette: keep the hue and saturation, change only the lightness, and every shade belongs to the same family.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
Which two values describe the same colour?
