Colour contrast and not relying on colour alone

Meet the WCAG contrast ratios for text and interface parts, check them reliably, and back up colour with other cues.

  • Beginner
  • 5 min read
  • Updated 17 Oct 2026

What you will learn

  • Check contrast against WCAG
  • Add non-colour cues to states and errors
  • Apply the 3:1 ratio to borders, icons and focus indicators

Before you start

Text that is too faint is hard to read for people with low vision, for older people, and for anyone using a phone in sunlight. Information shown only by colour is lost on people who cannot distinguish those colours. Both problems are easy to prevent once you know the rules.

This lesson covers the accessibility rules for colour. The CSS Complete Course covers colour syntax.

What a contrast ratio is

WCAG measures contrast as a ratio between the relative luminance of two colours. It runs from 1:1 (the same colour) to 21:1 (black on white). Hue does not matter, only lightness.

Text colour on whiteRatioNormal text at AA
#00000021:1Pass
#5959597:1Pass, and passes AAA
#7676764.54:1Pass, just
#9494943.03:1Fail for normal text, pass for large text
#cccccc1.61:1Fail

#767676 is the lightest plain grey that passes on pure white. Many "subtle" grey placeholders and captions are lighter than that and fail.

The text criteria

1.4.3 Contrast (Minimum), level AA

  • Normal text: at least 4.5:1 against its background.
  • Large text: at least 3:1. Large means at least 18 point (24 CSS pixels), or at least 14 point (about 18.66 CSS pixels) and bold.

Exceptions: text in logos, text in disabled controls, and purely decorative text that conveys nothing.

1.4.6 Contrast (Enhanced), level AAA

  • 7:1 for normal text and 4.5:1 for large text. Aim for this for long reading content.

Do not round. A ratio of 4.48:1 fails 4.5:1.

Non-text contrast: 1.4.11

Success criterion 1.4.11 Non-text Contrast (AA) requires 3:1 for:

  • Parts of controls needed to identify them, such as the border of a text input or the outline of a checkbox, against the adjacent colour.
  • States, such as the indicator showing a checkbox is ticked or which tab is selected.
  • Focus indicators.
  • Graphical objects needed to understand content, such as icons without text and the lines in a chart.
CSS
/* Fails: a pale grey border on white is about 1.6:1 */
.field input {
  border: 1px solid #cccccc;
}

/* Passes: about 3.5:1 against white */
.field input {
  border: 1px solid #8a8a8a;
}

A button whose label has good contrast does not need a contrasting border, because the text identifies it. A text input usually does, because its boundary shows where to type.

Checking contrast

  1. Browser DevTools. In Chrome, Edge and Firefox, inspect a text element and open the colour picker for its color; it shows the ratio and whether it passes AA and AAA.
  2. A dedicated checker. WebAIM's Contrast Checker and the Colour Contrast Analyser desktop app let you enter colours or pick them from the screen, including from images.
  3. Automated tools such as axe and Lighthouse flag many failures, but they cannot always work out the background behind text on images or gradients. Check those by hand at the lightest point.

Fix contrast in your design tokens, not one element at a time. Check each foreground and background pair in your palette once, and record which pairs are allowed for which sizes.

Check every state too: hover, focus, visited, disabled-looking but active, placeholder, and dark mode. A palette that passes in light mode often fails in dark mode.

Do not rely on colour alone: 1.4.1

Success criterion 1.4.1 Use of Color (A) says colour must not be the only visual means of conveying information, indicating an action, prompting a response or distinguishing an element. People with colour vision deficiencies, people using high contrast modes and people with monochrome displays may not see the difference.

Form errors

HTML
<!-- Colour only: the red border is the only sign of the error -->
<input id="email" class="is-invalid" type="email">

<!-- Text, an icon and colour together -->
<label for="email2">Email address</label>
<p id="email2-error" class="error">
  <svg aria-hidden="true" width="16" height="16" viewBox="0 0 16 16">
    <circle cx="8" cy="8" r="8" fill="currentColor"/>
  </svg>
  Error: enter an email address like name@example.com
</p>
<input id="email2" type="email" aria-invalid="true" aria-describedby="email2-error">

The second version uses words and an icon as well as colour. Lesson 9 explains aria-describedby and aria-invalid.

A link that is distinguished only by colour fails unless it has 3:1 contrast with the surrounding text and gains another cue on hover and focus. The simplest fix is to keep the underline.

CSS
main a {
  color: #1a5fb4;            /* 6.29:1 on white */
  text-decoration: underline;
  text-underline-offset: 0.15em;
}

main a:hover {
  text-decoration-thickness: 2px;
}

Status and charts

  • Add text to status colours: "Paid" and "Overdue", not just a green or red dot.
  • In charts, use patterns, direct labels or different marker shapes alongside colour.
  • For a required field, show the word "required", not a red asterisk with no explanation.

Colour and user settings

Some users switch on Windows contrast themes, which replace your colours with their own. The CSS forced-colors media feature lets you check that important cues survive.

CSS
@media (forced-colors: active) {
  .toggle[aria-pressed="true"] {
    /* Backgrounds are overridden, so add a border cue */
    outline: 2px solid CanvasText;
  }
}

Never remove a visible cue with forced-color-adjust: none unless you provide a replacement that also works.

Summary

  • Contrast is a luminance ratio from 1:1 to 21:1; do not round up.
  • Normal text needs 4.5:1 and large text 3:1 for AA (1.4.3); AAA asks for 7:1 and 4.5:1 (1.4.6).
  • Control boundaries, states, focus indicators and meaningful graphics need 3:1 (1.4.11).
  • Check every state and theme with DevTools or a dedicated checker.
  • Never use colour as the only cue (1.4.1): add text, icons, underlines or patterns.