Basic

Contrast and color that is not the message

Two rules carry most of the weight. Text must clear a contrast ratio against its background, and color must never be the only thing carrying meaning. Both are checkable with numbers, not opinions.

What you will see

The error style must set a weight or a symbol, not only a hue.

About 1 min of reading

Try It

Compute the ratio for one token pair

1

Pick the pair

grep -nE 'color:|--ink|--muted' public/css/main.css | head -5

Before you run it, say out loud what it will print. Then open the answer.

What it prints
The text token and the background token you want to check.
2

Compute the ratio

node -e 'const f=(c)=>{const v=parseInt(c.slice(1),16);const ch=[(v>>16)&255,(v>>8)&255,v&255].map(x=>{x/=255;return x<=0.03928?x/12.92:Math.pow((x+0.055)/1.055,2.4)});return 0.2126*ch[0]+0.7152*ch[1]+0.0722*ch[2]};const a=f("#767c85"),b=f("#ffffff");console.log(((Math.max(a,b)+0.05)/(Math.min(a,b)+0.05)).toFixed(2))'

Before you run it, say out loud what it will print. Then open the answer.

What it prints
A number. If it is under 4.5, that muted text fails on that background.
3

Check the dark theme too

node -e 'const f=(c)=>{const v=parseInt(c.slice(1),16);const ch=[(v>>16)&255,(v>>8)&255,v&255].map(x=>{x/=255;return x<=0.03928?x/12.92:Math.pow((x+0.055)/1.055,2.4)});return 0.2126*ch[0]+0.7152*ch[1]+0.0722*ch[2]};const a=f("#8b939c"),b=f("#101417");console.log(((Math.max(a,b)+0.05)/(Math.min(a,b)+0.05)).toFixed(2))'

Before you run it, say out loud what it will print. Then open the answer.

What it prints
A ratio for the dark theme. Both themes must clear 4.5 for body text.
4

Add the second channel

grep -n 'error' public/css/main.css

Before you run it, say out loud what it will print. Then open the answer.

What it prints
The error style must set a weight or a symbol, not only a hue.
Do not fix a failing ratio by making text larger alone. The threshold for large text is 3:1, but small text still needs 4.5.

Data Flow

Two colors to a verdict
1 Parse both colors to sRGB
↓
2 Linearize and compute luminance
↓
3 Ratio = (L1 + 0.05) / (L2 + 0.05)
↓
4 Pass if ratio meets the threshold for the text size
Why this works
How to compute a ratio

Convert both colors to relative luminance, then (L1 + 0.05) / (L2 + 0.05). The result is the ratio; compare it to 4.5 for body text.

Where this usually breaks

Placeholder gray text on a light background, and any state conveyed only by a hue shift. Both are cheap to fix and both appear in every audit.

Examples

The ratios

4.5:1 for normal body text, 3:1 for large text and for non-text UI like a focus ring or an icon. Those are pass or fail numbers, so compute them rather than eyeball them.

Redundant encoding

An error is red and also bold and also prefixed with a symbol. A link is colored and also underlined. The second channel is what makes it survive a color-blind reader.

Dark mode is the same test

Run the ratio check in both themes. A token pair that passes on white often fails on near-black, and the failure is usually the muted text color.

Words this lesson uses

Each one is defined here on this site. Open the entry for the full definition and the other words it points at.

← Reading order and landmarks
Previous Lesson
Reading order and landmarks
Next Lesson
Motion, sound, and time limits
Motion, sound, and time limits →