Color accessibility for brands

Accessible color palette: a WCAG contrast audit and extension of your brand colors

An accessible color palette is a set of brand colors with clear rules for which text, button, link, status and focus colors may sit on which background, each pairing measured against WCAG 2.2. I audit your existing brand palette, measure every pairing, and extend it with tonal scales that keep your hues. You get a list of approved pairings, UI states, a dark mode, design tokens and a usage guide. Your logo colors stay; the decision on every change is yours.

In short

What I do

  • Measure every foreground/background pairing of your palette against WCAG 1.4.3, 1.4.6 and 1.4.11, as a full contrast matrix.
  • Build tonal scales (50–950) from your own hues, and pick the steps for text, buttons, links, status messages and focus rings.
  • Define UI states (hover, pressed, focus, disabled), a dark mode, and status messages that do not rely on color alone (1.4.1).
  • Deliver design tokens as CSS, W3C DTCG JSON, Tailwind config, Adobe ASE swatches and a Figma variables JSON, plus a usage guide.

Not part of this service

  • A new brand identity. I work with your hues. Where a color has to get darker, I show you how far it moves; you decide.
  • Implementing the palette in your website or app. I deliver the tokens and the rules; your developers or agency apply them.
  • A full accessibility audit of your site. Color is one part of WCAG. Focus order, layout, forms and alt text are separate work.
  • Testing with people who have color vision deficiencies. I use a published simulation model, not user testing.
  • Legal advice. Whether a law applies to your company is for your lawyer to confirm.

Is this page for you?

This page is for you if one of these describes your situation:

  • An accessibility check flags low contrast on your website or online shop, and the failing colors come straight from your brand book.
  • You are a brand or design agency, and a client’s tender or EU customer asks for WCAG 2.2 AA. You need a palette that passes without losing the brand.
  • Your design system needs tokens for light and dark mode, and nobody has measured which combinations are actually allowed.
  • Your error and success messages are red and green text only, and you are not sure everyone can tell them apart.

Check your brand colors in one minute

  • Body text: enter your text color and background in the WebAIM Contrast Checker. Below 4.5:1, normal-size text fails WCAG AA.
  • Buttons: check the label color on your main button color. White on a bright orange, teal or yellow often lands near 2:1.
  • Focus: press Tab on your website. If you cannot see where you are, or the ring is a pale tint, keyboard users cannot either.
  • Status: switch your phone to grayscale and open a form with an error. If the error looks like any other text, it relies on color alone.

Read on for the technical detail. Below: which WCAG criteria concern a palette, a measured before-and-after example on a fictional coffee brand with every file downloadable, and the rules in the EU and the US. To skip ahead, send your brand book or color list and I will tell you which pairings fail.

What makes an accessible color palette

A palette is accessible when every combination your brand actually uses meets its WCAG threshold, and when no information depends on color alone. Single colors are never “accessible” on their own; only pairings are.

Text contrast: 1.4.3 and 1.4.6

WCAG 2.2 measures contrast as a ratio between the relative luminance of two colors, from 1:1 to 21:1. Success criterion 1.4.3 (Level AA) asks for 4.5:1 for normal text and 3:1 for large text (about 24 px, or about 18.7 px bold). 1.4.6 (Level AAA) raises this to 7:1 and 4.5:1. AA is the level laws and tenders usually cite; I mark which pairings also reach AAA.

Buttons, input borders and icons: 1.4.11

Non-text contrast, 1.4.11 (AA), asks for 3:1 for the visual parts needed to recognize a control or its state: an input border, a checkbox, the edge of a button that has no text label, a focus ring, the meaningful parts of an icon. A pale grey border on white is the typical failure.

Color alone: 1.4.1

Use of Color, 1.4.1 (Level A), says color must not be the only way to convey information. A red message and a green message that differ only in hue fail, however good their contrast. An icon and a word such as “Error:” fix that; the palette then only has to support them.

Focus rings: 2.4.13, and what 2.4.11 is not

Focus Appearance, 2.4.13 (AAA, new in WCAG 2.2), asks for a focus indicator of sufficient size with 3:1 contrast between focused and unfocused states. A palette can supply the colors and the ring definition; only the implemented page can prove the size. 2.4.11, Focus Not Obscured, is often confused with this: it is about sticky headers and overlays covering the focused element, a layout matter that no palette can solve.

Evidence: a fictional coffee brand, before and after

This is a measured worked example on a fictional brand, not client work. I invented “Örnek Kahve Co.” and its brand-book palette with typical brand-book choices, measured it, rebuilt it, and checked the result with independent tools. Every figure, report and file below is labelled “Fictional example brand”.

Source and tools

  • Before palette: white #FFFFFF, cream #FFF8F0, primary orange #F28C28, secondary teal #2EC4B6, accent yellow #FFD166, grey text #A0A0A0, espresso #4A3428, status red #E74C3C and green #2ECC71 (no icons), pale focus ring #CDEFEB.
  • Color maths: WCAG 2.x ratio from sRGB relative luminance; tonal scales in OKLCH (coloraide 8.13), hue kept, lightness solved for a fixed contrast target against white.
  • Color vision simulation: the matrices of Machado, Oliveira and Fernandes (2009), severity 1.0, applied in linear sRGB.
  • APCA: cross-checked against npm apca-w3 0.1.9 and reported as information only. APCA is not a WCAG 2.2 requirement.
  • Typeface in figures and guide: Inter (SIL Open Font License 1.1).

What was wrong, measured

The brand book prescribed 14 uses (body text, button labels, links, messages, focus ring, borders). One of the 14 met its threshold: espresso on yellow, 8.03:1.

Brand-book pairings, before (WCAG 2.x ratio; APCA Lc for information)
Use in the brand bookColorsRatioNeeded
Body and caption text on white#A0A0A0 on #FFFFFF2.61:1 (Lc 51.0)4.5:1
Primary button label#FFFFFF on #F28C282.45:1 (Lc −52.8)4.5:1
Secondary button label#FFFFFF on #2EC4B62.17:14.5:1
Links and prices#F28C28 on #FFFFFF2.45:14.5:1
White badge on yellow#FFFFFF on #FFD1661.44:14.5:1
Error message text#E74C3C on #FFFFFF3.82:14.5:1
Success message text#2ECC71 on #FFFFFF2.10:14.5:1
Focus ring against the page#CDEFEB on #FFFFFF1.23:13:1
Focus ring against the primary button#CDEFEB on #F28C282.00:13:1
Promo banner text#4A3428 on #FFD1668.03:14.5:1

The remaining four (body text on cream, accent headings, button edge, input border) failed too; all 14 are in the measurements file. In typical vision, the red and green status colors differ strongly (CIEDE2000 72.35). Under simulated deuteranopia that difference drops to 11.64.

What I changed

For each hue I built an 11-step scale in OKLCH and picked the steps that pass for each role. The brand orange, teal, yellow and cream stay unchanged for the logo, illustration and large fills. Text on brand orange or yellow uses a dark neutral (6.48:1 and 11.03:1).

Brand-book colors and their accessible replacementsSeven roles. Each original color fails against white; each replacement passes. Values are listed in the table below. Role Brand book (vs white) Accessible role (vs white) ΔE2000 Button / CTA #F28C28 ✕ 2.45:1 Fail #AC5D00 orange-600 ✓ 4.86:1 AA 18.4 Links #F28C28 ✕ 2.45:1 Fail #944F00 orange-700 ✓ 6.22:1 AA 26.0 Accent headings #2EC4B6 ✕ 2.17:1 Fail #006D65 teal-700 ✓ 6.22:1 AA 29.0 Secondary text #A0A0A0 ✕ 2.61:1 Fail #846C60 neutral-600 ✓ 4.89:1 AA 20.3 Error #E74C3C ✕ 3.82:1 Fail #911B12 red-800 ✓ 8.86:1 AAA 21.5 Success #2ECC71 ✕ 2.10:1 Fail #007F76 teal-600 ✓ 4.89:1 AA 29.4 Focus ring #CDEFEB ✕ 1.23:1 Fail #00544E teal-800 ✓ 8.83:1 AAA 52.4 Fictional example brand · WCAG 2.x contrast ratio against #FFFFFF
Roles before and after: contrast against white and CIEDE2000 shift
RoleOriginal → accessibleBeforeAfterΔE2000
Button / call to action#F28C28 → orange-600 #AC5D00, white label2.45:14.86:118.4
Link#F28C28 → orange-700 #944F002.45:16.22:126.0
Accent#2EC4B6 → teal-700 #006D652.17:16.22:129.0
Secondary text#A0A0A0 → neutral-600 #846C602.61:14.89:120.3
Error#E74C3C → red-800 #911B123.82:18.86:121.5
Success#2ECC71 → teal-600 #007F762.10:14.89:129.4
Focus ring#CDEFEB → teal-800 #00544E with a 2 px white inner ring1.23:18.83:152.4

The honest part: a CIEDE2000 difference of about 2 is just noticeable. Shifts of 18 to 29 are clearly visible. The hue stays, the lightness does not: the button orange becomes a deeper amber-brown. Whether that is still your brand is a brand decision, and it is yours. In a client job, you see these numbers and swatches before anything is final.

Örnek Kahve Co. brand colors before and after, fictional example brand. Top: ten brand-book colors, each with its contrast ratio against white; only espresso brown passes, at 11.58 to 1. Middle: six tonal scales from 50 to 950 for orange, teal, yellow, neutral, red and green, every step labelled with its ratio, the steps used by tokens outlined. Bottom: a table of seven roles showing how far each accessible color moved from the original, CIEDE2000 18.4 to 52.4.
The palette before, the tonal scales after, and the shift of each role. Every step shows its measured ratio against white.

UI states, dark mode and status messages

  • Hover and pressed: orange-700 (6.22:1 with white) and orange-800 (8.84:1).
  • Focus: a 3 px outer ring in teal-800 plus a 2 px white inner ring. Outer ring against the page 8.83:1, against cream 8.38:1; the white inner ring against the orange button 4.86:1.
  • Disabled: label at 2.17:1 (light) and 1.80:1 (dark), reported for information. Inactive components are exempt from 1.4.3.
  • Status messages: now carry an icon and the words “Hata:” or “Tamam:” (the example brand’s interface is Turkish), so color is no longer the only cue.
  • Dark mode: its own set of approved pairings, all re-measured; in dark mode the action color resolves to #ed8c33.

What the verification reports after

Contrast results, before and after
MeasuredBeforeAfter
Brand-book uses meeting their threshold1 of 1414 of 14 (same uses, new roles)
Full matrix, 45 pairs: AA normal text (4.5:1)717
Full matrix: AA large text / UI (3:1)1221
Full matrix: AAA (7:1)47
Approved pairings, light mode (16 text, 11 non-text)–27 of 27 pass; lowest text 4.61:1, lowest UI 3.04:1
Approved pairings, dark mode–27 of 27 pass; lowest text 6.32:1, lowest UI 3.91:1
Text pairings that also reach AAA–light 6 of 16, dark 10 of 16
Two 10 by 10 contrast matrices side by side, fictional example brand. Each cell shows the WCAG ratio of a text color on a background color, labelled AAA, AA, 3+ or Fail. Before: of 45 color pairs, 7 pass AA normal text, 12 pass AA large or UI, 4 pass AAA. After: 17 pass AA, 21 pass AA large or UI, 7 pass AAA. Most cells still fail in both.
Contrast matrix before and after. Most cells still fail, by design: the approved-pairings list is the contract, not the matrix.

For the status colors, the search kept only error and success candidates that reach 4.5:1 on white and cream, then chose the pair with the largest minimum difference across the three simulations.

Error vs success color, CIEDE2000 difference (Machado 2009, severity 1.0)
PairTypicalProtanDeutanTritan
Before: #E74C3C vs #2ECC7172.3526.3211.6472.28
After, light: red-800 vs teal-60054.1327.2829.9257.20
After, dark: red-400 vs teal-10043.8528.7528.7250.89

The gain comes from moving success to a bluish green. The lightness ratio between the two is about the same as before (1.81:1 against 1.82:1), so color remains a secondary cue. The icon and the word carry the meaning.

Form error and success messages shown in typical vision and simulated protanopia, deuteranopia and tritanopia, fictional example brand. Before: red and green text without icons; under deuteranopia both turn a similar olive, CIEDE2000 11.6. After, light and dark: each message has an icon and the label Hata or Tamam, with differences of 27.3 to 57.2.
Status messages under simulated color vision deficiencies, before and after, in light and dark mode.

How the results were cross-checked

Show the ten cross-checks (tool, version, result)
Cross-checks with independent tools
CheckResult
My WCAG ratios vs axe-core 4.13.0 on 180 rendered pairslargest difference 0.01 (rounding)
axe-core 4.13.0 on the usage guide (WCAG 2.0, 2.1, 2.2 A/AA rules)0 violations, 0 incomplete, 18 rules passed
axe-core 4.13.0 on the before and after matrix pages0 violations each
APCA values vs npm apca-w3 0.1.9, 205 pairsidentical (difference 1.4 × 10⁻¹⁴)
Color vision simulation vs coloraide 8.13, 76 colors × 3identical to the 8-bit value
DTCG tokens.json117 tokens, 0 unresolved aliases; Style Dictionary 5.6.0 builds 117 CSS variables
tokens.css in Chromium, light and dark--color-action resolves to #ac5d00 / #ed8c33
palette.aseindependent parser (swatch 0.4.0) reads 9 groups, 116 swatches
figma-variables.json94 variables, 117 mode values, 0 dangling aliases
tailwind.config.jsloads in Node 22

What the checks do not report

Thin, small type

The WCAG ratio ignores font weight, size and anti-aliasing. A thin 12 px label can pass 4.5:1 and still be hard to read. APCA addresses part of this, but it is not a WCAG 2.2 requirement, so I report it for information.

Real eyes and screens

The simulation models full dichromacy with one 2009 method. Anomalous trichromacy, low vision and real displays vary. No user testing was done for this example.

Import into Figma and Adobe

The Figma payload and the ASE file were validated for structure only, not imported into Figma or an Adobe app. Figma’s variables REST endpoint is limited to Enterprise plans.

Your real product

axe-core checks the rendered contrast of the guide. It cannot tell whether your product uses color alone somewhere, or whether a sticky header hides the focus ring (2.4.11).

And a number is not a brand decision. ΔE2000 tells you how far a color moved; whether it still feels like your brand is for you to judge.

Download the files and compare

Worked example — downloads
FileWhat it isSize
Worked example (ZIP)All files below plus the token files (tokens.json, tokens.css, tailwind.config.js, palette.ase, figma-variables.json), the HTML contrast matrices, the HTML usage guide, measurements.json, findings and the scripts. These file types are available only inside the ZIP.2.8 MB
Usage guide (PDF)Two A4 pages: brand colors, working tokens, rules, UI states, approved pairings, dark mode, limits177 KB
Approved pairings (CSV)27 light and 27 dark pairings with ratios4.6 KB
Contrast matrix, before (CSV)90 ordered pairings of the brand-book palette6.4 KB
Contrast matrix, after (CSV)90 ordered pairings of the accessible roles6.6 KB

What you receive

You receive a measured palette, the rules for using it, and the same palette as files your designers and developers can load.

  • A contrast matrix of your current palette (before) and of the extended one (after), as HTML and CSV.
  • Tonal scales built from your hues, with the measured ratio of every step.
  • A list of approved pairings for light and, if you need it, dark mode, with UI states and focus ring.
  • Design tokens: CSS custom properties, W3C DTCG JSON, Tailwind config, Adobe ASE swatches, Figma variables JSON.
  • A usage guide as HTML and PDF, and a findings report that lists every color shift (ΔE2000) and what the checks cannot see.

How the work runs

You send your brand colors first; the price is fixed before any work starts.

  1. Send your brand book or color list. A PDF of the brand guidelines, your CSS, or a screenshot of the key screens is enough. I tell you which pairings fail and by how much.
  2. You receive a scope and a fixed price, including the modes and token formats you need.
  3. I do the work. You receive a preview of the palette and guide, marked as such, with the contrast report and every proposed color shift for your approval.
  4. Your approval releases the final files: tokens, guide, matrices and findings report.

Pricing

I price each job after seeing the material. Effort depends on the number of colors and roles, whether you need a dark mode, and which token formats your tools use; an existing design system with named tokens is quicker than a brand book with loose hex codes. You get a fixed price before work starts, with no hourly meter.

Why this matters now

Low-contrast text is the failure found most often in WebAIM’s yearly scan of one million home pages, and since 28 June 2025 the European Accessibility Act makes WCAG-level accessibility a legal matter for many consumer-facing websites and online shops in the EU. What that means for your company is a legal question; this page covers the technical side.

A palette fixed at the token level fixes every page that uses it. That is usually cheaper than correcting contrast one page at a time.

Frequently asked questions

Will our brand colors change?

Your logo and brand colors stay for logo, illustration and large fills. Text, link, button and status roles get darker steps of the same hue, and those shifts are clearly visible (CIEDE2000 18 to 29 in the example). You see every shift before approving, and the brand decision is yours.

Why doesn’t every color combination pass afterwards?

Because it does not need to. In the example, 17 of 45 pairs pass AA text after the rework; the rest are combinations nobody should use for text. The approved-pairings list, 27 of 27 passing in light and dark mode, is what your team works from.

What about APCA and WCAG 3?

I calculate APCA values and include them for information, because they say more about thin and small type. WCAG 2.2 uses the contrast ratio, and that is what laws and tenders cite today. The palette is built to pass WCAG 2.x.

Can we import the tokens into Figma, Adobe and our code?

The files follow the published formats, and in the example their structure was checked with independent tools: Style Dictionary builds the CSS, an independent parser reads the ASE, and the Figma file is a variables REST API payload. They were not imported into Figma or an Adobe app, and Figma’s variables API needs an Enterprise plan. I tell you what to expect for your tool before you approve.

Does the color-blindness simulation mean the palette works for everyone?

No. It is a published model of full dichromacy, not a test with people. That is why status messages get an icon and a word: then the meaning no longer depends on telling two colors apart.

Does an accessible palette make our website accessible?

It removes one common class of failures, if the tokens are applied as specified. Focus order, a focus ring hidden by a sticky header, forms, alt text and keyboard use are separate topics. An accessibility audit of the implemented site is separate work.

Related services

Send me your brand colors

A brand book, a list of hex codes, or a screenshot of your main page. You get back a straight account of which pairings fail, by how much, and how far each color would have to move.

Ali Karabüyük · Tekirdağ, Türkiye · working remotely with clients worldwide · document and image production since 2004, professional practice since 2008.