DevLab
Colors

Color Contrast for Developers: WCAG Rules and How to Check

Learn the WCAG contrast requirements for text, icons, and interactive elements — and how to test contrast ratios in your projects.

Why Contrast Matters

Contrast is the difference in luminance between foreground and background. Low contrast means text is hard to read — not just for people with vision impairments, but for anyone using a screen in bright sunlight, on a low-quality monitor, or while tired. WCAG (Web Content Accessibility Guidelines) sets minimum contrast ratios that all web content should meet.

WCAG Contrast Ratios

WCAG defines two conformance levels for text contrast:

  • AA (minimum) — 4.5:1 for normal text, 3:1 for large text (18px bold or 24px regular)
  • AAA (enhanced) — 7:1 for normal text, 4.5:1 for large text

Non-text elements (icons, borders, form inputs) require at least 3:1 contrast against their background under WCAG 2.1 Success Criterion 1.4.11.

A contrast ratio of 1:1 means identical colours (no contrast). 21:1 is the maximum (black on white). Most body text should aim for at least 7:1 to be comfortably readable.

How Contrast Is Calculated

The contrast ratio formula uses relative luminance — a measure of how bright a colour appears to the human eye. The formula is: (L1 + 0.05) / (L2 + 0.05) where L1 is the lighter colour's luminance and L2 is the darker.

Relative luminance is not a simple average of RGB values. It weights green heavily (the human eye is most sensitive to green) and applies a gamma correction. This is why #808080 (grey) has a luminance of about 0.22, not 0.50.

Common Contrast Mistakes

  • Light grey on white#999 on #FFF gives 2.85:1 (fails AA). Use #767676 for 4.54:1 (passes AA).
  • Placeholder text — Many designs use very light placeholder text. If the placeholder conveys important information (like a format hint), it must pass 4.5:1.
  • Coloured links without underline — If links rely only on colour to be distinguishable from surrounding text, the link colour must have 3:1 contrast against the body text colour AND meet contrast requirements against the background.
  • Dark mode inversions — Flipping a light theme to dark often breaks contrast. Pure white text on pure black (#FFF on #000 = 21:1) can cause halation (glowing edges) for some readers. Aim for 15:1 to 18:1 in dark mode.

Testing Contrast in Practice

Use the DevLab Color Contrast Checker to test any two colours instantly. For automated testing across a full site, tools like axe-core, Lighthouse, and Pa11y scan every element and flag contrast failures. In Chrome DevTools, inspect any element and the colour picker shows the contrast ratio inline.

For design systems, define your colour tokens as HSL or OKLCH variables, then write a script that checks every foreground/background combination against WCAG AA. Run it in CI so contrast regressions are caught before deploy.

Practice with these tools

More Learning Topics

RegexRegex Basics: A Complete Beginner's GuideRegexRegex Special Characters: Complete ReferenceRegexRegex Groups and Captures ExplainedRegexRegex Quantifiers: Complete GuideCSSCSS Selectors: The Complete GuideCSSCSS Specificity: Why Your Styles Aren't ApplyingJSONJSONPath Syntax: Query JSON Like XPathTimeUnix Timestamps ExplainedEncodingBase64 Encoding ExplainedEncodingJWT Structure and How It WorksEncodingJWT vs Session Tokens: Which Should You Use?EncodingJWT Refresh Tokens ExplainedCryptoHash Functions Explained: MD5, SHA-256, and When to Use EachEncodingURL Encoding Explained: What %20 Actually MeansJSONJSON Schema Explained: Validate Your JSON DataJSONJSON vs YAML: Which Should You Use?JSONJSON.stringify and JSON.parse: Edge Cases You Should KnowRegexRegex Lookahead and Lookbehind: Match Without ConsumingRegexRegex for Email Validation: The Right ApproachCSSThe CSS Box Model: margin, padding, border, and contentCSSFlexbox vs CSS Grid: When to Use EachCSSCSS Custom Properties (Variables) ExplainedTimeISO 8601 Explained: The Right Way to Format DatesTimeUnix Timestamps vs ISO 8601: Which to Use in Your API?EncodingUTF-8 Explained: How Computers Store TextTextCORS Explained: Why Your API Call is BlockedTextHTTP Status Codes: A Practical Developer GuideRegexNamed Capture Groups in Regex: Clean ExtractionsColorsColor Spaces Explained: RGB, HSL, HEX, and BeyondNumbersNumber Bases Explained: Binary, Octal, Decimal, and HexNumbersBitwise Operations for Web DevelopersHTMLHTML Semantic Elements: A Complete ReferenceTextText Encoding for Developers: ASCII, UTF-8, and UnicodeRegexWhat is Regex? Complete Guide for DevelopersJSONJSON Format Explained: Structure, Syntax, and Common ErrorsEncodingHow JWT Works: Header, Payload, Signature DecodedHTMLHow to Convert Any Website to Markdown (for LLMs, RAG & Docs)TextPreparing Website Content for RAG: Clean Markdown Pipelines