What is WCAG color contrast?

WCAG color contrast: WCAG color contrast is the minimum luminance ratio between text or UI and its background: 4.5:1 for body text, 3:1 for large text and UI parts at AA.

The Web Content Accessibility Guidelines define contrast as a ratio between the relative luminance of two colors, from 1:1 (identical) to 21:1 (black on white). Enough contrast keeps text readable for people with low vision or color vision deficiencies, on dim screens and in sunlight. Most accessibility laws and procurement standards reference WCAG level AA, so its numbers are the practical baseline for any product.

The thresholds

ContentAAAAA
Normal text4.5:17:1
Large text (at least 24px, or about 18.66px bold)3:14.5:1
UI components and meaningful graphics3:1No additional requirement

Text contrast is success criterion 1.4.3 (AA) and 1.4.6 (AAA). Non-text contrast, 1.4.11, covers the visual boundaries that identify inputs and buttons, focus indicators, icons that convey meaning, and chart elements needed to understand the data. Disabled controls, pure decoration and logos are exempt.

How it is calculated

Each color is converted to relative luminance, a weighted sum of linearized sRGB channels in which green counts most and blue least. The ratio is (L1 + 0.05) / (L2 + 0.05), with the lighter color as L1. You rarely compute it by hand; use the contrast checker or browser devtools, which show the ratio in their color pickers.

ts
type RGB = [number, number, number]

function luminance([r, g, b]: RGB) {
  const lin = (c: number) => {
    const s = c / 255
    return s <= 0.04045 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4
  }
  return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b)
}

export function contrastRatio(a: RGB, b: RGB) {
  const [hi, lo] = [luminance(a), luminance(b)].sort((x, y) => y - x)
  return (hi + 0.05) / (lo + 0.05)
}

Common pitfalls

  • Light gray placeholder text. If it carries information, it needs the same 4.5:1 as body text.
  • Faint input borders. A border that is the only thing identifying a field needs 3:1 against the adjacent background.
  • Text over images, gradients or glass. Check the worst spot behind the text, not the average.
  • Checking only the light theme. Dark mode needs its own pass.
  • Using color alone to signal state, which fails 1.4.1 Use of Color regardless of contrast.

Drafts of WCAG 3 explore a different contrast model, APCA, that accounts for font size, weight and polarity. It is not a standard yet; conformance today is measured with the WCAG 2 ratio.

How MiniDev UI handles it

The kit sets contrast targets in its tokens: fg-muted is tuned for at least 7:1 and fg-subtle for at least 4.5:1, in both themes. Focus rings use a 2px accent outline, and status colors are tuned in OKLCH for equal weight. Status badge pairs color with a dot and a text label, so state never depends on hue alone.

Status BadgeuiTailwind status badge with a tinted fill, hairline border and colored dot in neutral, success, warning, danger, accent and info tones that read as one set.npx shadcn@latest add ui.minidev.pro/r/status-badge.json