Home / Snippets / Color & Theming /

Accessible contrast pair

Define background and text color pairs using oklch() that reliably hit WCAG AA (4.5:1) or AAA (7:1) contrast ratios by controlling the lightness channel.

Deep navy / near-white
oklch(0.18 …) → oklch(0.95 …) · ~14:1
AAA
Rich purple / light lavender
oklch(0.22 …) → oklch(0.92 …) · ~11:1
AAA
Accent purple / white
oklch(0.42 …) → oklch(0.98 …) · ~5.5:1
AA
Mid-grey / off-white
oklch(0.36 …) → oklch(0.92 …) · ~5:1
AA
Light grey / mid-grey — fails
oklch(0.70 …) → oklch(0.55 …) · ~1.9:1
Fail
Widely Supported
colorno-js

Quick implementation

/* Accessible contrast pair using oklch() */
/* Rule of thumb: lightness difference >= 0.50 hits AA,
   >= 0.65 hits AAA for neutral chroma values.       */

:root {
  /* AAA pair — ~14:1 ratio */
  --pair-bg:   oklch(0.18 0.04 260);
  --pair-text: oklch(0.95 0.01 260);

  /* AA pair — ~5.5:1 ratio */
  --pair-accent-bg:   oklch(0.42 0.18 265);
  --pair-accent-text: oklch(0.98 0.005 260);
}

.accessible-surface {
  background: var(--pair-bg);
  color: var(--pair-text);
}

.accessible-surface--accent {
  background: var(--pair-accent-bg);
  color: var(--pair-accent-text);
}

Prompt this to your LLM

Includes role, constraints, two variants, and edge cases to handle.

You are a senior frontend engineer building an accessible color system.

Goal: Define CSS custom properties for background/text color pairs that
meet WCAG 2.2 contrast requirements — at least AA (4.5:1) for normal text,
AAA (7:1) where possible — using oklch() throughout.

Technical constraints:
- Use oklch(lightness chroma hue) exclusively — no hex, hsl, or rgb values.
- Rely on the lightness channel to control contrast. A lightness delta of
  ~0.50 between background and text reliably passes AA for neutral chroma;
  ~0.65+ reliably passes AAA.
- Define pairs as CSS custom properties grouped on :root.
- Name pairs semantically: --surface-bg / --surface-text,
  --accent-bg / --accent-text, etc.
- Keep hue consistent within a pair for visual harmony; vary only lightness
  (and optionally chroma) to create the contrast.
- Include a comment showing the approximate contrast ratio for each pair.

Variant A — Design tokens only:
Return :root custom properties for three pairs:
  1. A dark surface AAA pair (~14:1)
  2. A branded/accent AA pair (~5:1)
  3. A subtle muted AA pair (~4.5:1)

Variant B — Component classes:
Wrap each pair in a utility class (.surface, .surface--accent, .surface--muted)
that sets background and color from the matching custom properties, plus
border-radius: 0.5rem and padding: 1rem 1.25rem.

Edge cases to handle:
- High-chroma oklch colors can appear lighter or darker than their L value
  suggests in sRGB — verify pairs with a contrast checker tool such as
  the APCA contrast algorithm or WhoCanUse.com.
- oklch() is supported in all modern browsers (Chrome 111+, Firefox 113+,
  Safari 15.4+), but test in Safari on iOS where color space handling
  can differ from desktop.
- For large text (18pt+ or 14pt+ bold), the WCAG AA threshold drops to
  3:1 — document which pairs are intended for large vs normal text.

Return CSS only (custom properties + classes if Variant B).

Why this matters in 2026

Color contrast failures remain one of the most common WCAG violations across the web — not because designers don't care, but because traditional color spaces like HSL and hex make contrast unpredictable. Two colors can share the same HSL lightness value yet have a 3:1 contrast difference in practice because HSL doesn't account for perceptual brightness. oklch() changes this fundamentally. Because its lightness axis is perceptually uniform, the difference between two lightness values maps directly onto perceived brightness difference — and therefore onto contrast ratio in a reliable, predictable way.

With native oklch() support now universal across browsers, there's no longer a reason to reach for hex or HSL when building accessible color systems. You can define contrast pairs with mathematical confidence, document the rationale as code comments, and hand off a palette that designers and engineers can extend without breaking accessibility.

The logic

WCAG 2.2 defines two contrast thresholds for normal-weight text at body sizes: AA at 4.5:1 and AAA at 7:1. The ratio is computed from the relative luminance of the two colors — a value derived from linear RGB that loosely maps to perceived brightness.

In oklch(), the first argument is the lightness value on a scale from 0 (black) to 1 (white). Because oklch uses a perceptually uniform model, you can treat the lightness delta between a background and foreground as a strong proxy for contrast distance. As a rule of thumb: a lightness difference of 0.50 or more consistently clears AA for neutrals and low-chroma values; 0.65 or more consistently clears AAA.

Chroma interacts with this too. High-chroma colors — vivid purples, saturated greens — have slightly different luminance characteristics than their neutral counterparts at the same lightness. This means a pair like oklch(0.42 0.18 265) (a vivid purple) paired with white needs verification with a real contrast tool, not just a lightness delta calculation. As chroma approaches zero, the lightness-to-luminance mapping becomes near-perfect, making neutral greys the most reliable candidates for contrast pairs.

To derive a pair programmatically: pick a background lightness, add your target delta (0.50 for AA, 0.65 for AAA) to get the foreground lightness, and keep hue and chroma identical or close. The result is a harmonious pair that shares its hue identity while meeting the contrast threshold.

Accessibility & browser support

oklch() is supported in Chrome 111+, Firefox 113+, and Safari 15.4+, covering all current browser versions as of 2026. There is no fallback needed for modern targets. If you must support older browsers, provide a color: #fff fallback before the oklch() declaration — browsers that don't understand oklch() will use the previous valid value.

One nuance: Safari on iOS historically handled color gamut slightly differently than desktop Safari. For P3-wide colors (chroma above roughly 0.18 in oklch), test on a physical iOS device in addition to simulators. For contrast pairs using neutral or near-neutral chroma, this is a non-issue.

Beyond WCAG 2.2, the emerging APCA (Accessible Perceptual Contrast Algorithm) proposes a more nuanced contrast model that accounts for font weight, size, and spatial frequency. APCA pairs may differ from WCAG pairs for small or bold text. For most production use, targeting WCAG AA (4.5:1) remains the accepted legal and practical standard.