Building a color palette that passes WCAG accessibility requirements is not about sacrificing aesthetics — it is about understanding how luminance works and making deliberate choices at the scale-design stage. Retrofitting contrast into an existing palette is painful; designing for it from the start takes the same amount of time and produces far better results.
Why most palettes fail contrast checks
The root cause is almost always the same: the palette was designed by eye in a saturated, idealized environment — a bright monitor, a design tool canvas with a neutral grey background — where the eye naturally compensates for low luminance differences. When that palette reaches a real product with white page backgrounds and body-sized text, the contrast ratios collapse.
A second common mistake is designing a single "brand color" and using it for everything: buttons, links, text, highlights, and borders. A mid-tone hue at around 50% lightness typically achieves around 3–4:1 on white — which passes for large text and UI components, but fails for normal body text and small labels.
The accessible palette design process
Think in terms of roles rather than individual colors. Every palette needs colors that fulfill specific contrast roles:
- Body text: must achieve ≥ 4.5:1 on the page background (AA normal text).
- Secondary / muted text: must achieve ≥ 4.5:1 on the page background. This is where most designs fail — muted text is still text.
- Interactive elements (links, buttons): text on the element ≥ 4.5:1; element border/background ≥ 3:1 against the surrounding area.
- Focus indicators: ≥ 3:1 against adjacent colors (WCAG 2.2 criterion 2.4.11 adds further constraints).
- Decorative elements, illustrations: no contrast requirement — decorative content is exempt.
Building a contrast-safe color scale
The most reliable method is to build a lightness scale for each hue. A scale of 11 steps — typically labeled 50, 100, 200, … 900, 950 — gives you a predictable set of shades where you can identify in advance which steps pass which contrast thresholds against white and against dark backgrounds.
A practical approach in HSL: fix the hue (H) and saturation (S), then vary the lightness (L) from 97% down to 10% in 11 steps. For most hues:
- Steps 700–950 (L ≤ 35%) will achieve ≥ 4.5:1 on white → usable for normal text on a light background.
- Steps 500–600 (L ≈ 45–55%) will achieve ≥ 3:1 on white → usable for large text and UI components on a light background.
- Steps 50–300 (L ≥ 70%) will achieve ≥ 4.5:1 on step 900 → usable for text on a dark background.
Handling your brand primary color
Most brand primary colors live in the mid-tone range because saturated, mid-lightness colors read as vivid and energetic on screen. If your brand primary is something like a mid-blue (#2563EB), a teal, or a coral, you have a few options:
- Use the brand color as a background, not text: a vivid button background paired with white text at ≥ 4.5:1 is accessible. The brand color fills the role of a container, not a foreground.
- Darken the brand color for text usage: create a "text variant" at 60–70% of the original lightness. This shade can carry body text while the original brand color is reserved for backgrounds and decorative uses.
- Introduce a neutral dark for body text: do not try to use the brand color for body copy. A near-black neutral (e.g.,
#1A1A2E) satisfies the 4.5:1 requirement trivially and keeps typography readable without brand dilution.
Dark mode palettes and accessible contrast
Dark mode does not simply invert the contrast problem — it changes it. On a dark background (e.g., #121212), a pure white (#FFFFFF) achieves 19.6:1, which is the maximum and can cause visual fatigue. The recommended practice is to use a slightly off-white (#E8E8E8) which achieves around 15:1 — still far above AAA but less visually harsh.
For secondary text in dark mode, aim for shades in the 60–75% lightness range — these typically hit the 4.5:1 threshold against a dark background while reading as visually softer than the primary text.
Documenting accessible color pairs
A palette document that lists hex values without specifying valid usage pairs is incomplete. Every design system handoff should include an explicit list of approved color pairs — foreground + background — with their contrast ratio and the WCAG level achieved. This prevents developers from inadvertently picking two colors from the palette that were never tested together.
A simple format: list each approved pair as a row with the foreground hex, background hex, ratio, and a pass/fail note for AA and AAA at both normal and large text sizes. This becomes the single source of truth for contrast decisions across the product.