contrast-color() shipped in all three engines this spring (Chrome 147, Firefox 146 and Safari 26) and reached Baseline Newly Available status in April 2026. Smashing Magazine has a long write-up by Durgesh Pawar on the spec, the APCA situation and the gotchas. I care about a narrower, WordPress-specific piece of it. Users can edit the color palette, and hardcoded text colors sitting on those presets are a bug I keep running into.
It usually goes like this. A theme ships primary as #1e40af with white text on buttons, tested and fine. Months later someone adds a light yellow accent in the Site Editor, drops a button on the homepage, and the label is white on near-white. Nothing throws an error, so nobody notices. Tag chips and sale badges break the same way. WebAIM found low contrast text on 83.9% of homepages in 2026, and palette drift like this is an easy way to end up in that number.
What contrast-color() gives you
The Level 5 version is one color in, black or white out, whichever contrasts more. Every theme.json palette entry becomes a CSS custom property, so wiring it into the button block is two declarations plus a guard:
.wp-block-button__link {
background: var(--wp--preset--color--primary);
color: #fff;
}
@supports (color: contrast-color(red)) {
.wp-block-button__link {
color: contrast-color(var(--wp--preset--color--primary));
}
}
Some notes on that. The plain fallback comes first, because a browser without support drops the whole color line and keeps the earlier one. This has to live in your stylesheet and not in theme.json, since the theme.json styles schema can’t express @supports. If you want the same flip inside the editor, enqueue the rules for the editor view too, with add_editor_style or wp_enqueue_block_style. I’d check it in the iframe instead of assuming it works.
How I wire it into a theme
- Leave the palette where it is, in settings.color.palette. Nothing changes there.
- Add the @supports block to the stylesheet and target the presets that carry text: the button link, sale badges, tag chips.
- Leave brand-critical pairings explicit. If the design says navy with white, write white.
- Run an automated contrast check before shipping, for reasons in the next section.
This fits the move toward styling buttons through theme.json instead of fighting the cascade, which I wrote about when button hover states landed in theme.json. If you want help retrofitting a palette this way, or untangling a store where badge colors clash with the brand, that’s the kind of work I do and I’m happy to take a look.
Where it stops helping
The output is only black or white. A mid blue like #2277d3 passes AA both ways, and the browser gives you whichever is a hair better, which may not be the pairing the brand wants. For backgrounds with luminance between roughly 10% and 30%, neither black nor white reaches the AAA ratio of 7:1, so you’re picking the least bad option. Gradients and images are out entirely, because the function takes a flat color.
There’s also an annoyance on the tooling side. Scanners like Lighthouse and axe compare the computed color against the background color, so a text-shadow fallback still gets flagged even when it reads fine to a person. If your pipeline fails builds on contrast, expect to annotate that rule.
The math can move under you
The spec leaves the algorithm up to the browser on purpose. Every engine uses WCAG 2.x relative luminance today, and the successor hasn’t been decided. APCA was pulled from the WCAG 3 draft in mid-2023, and the W3C timeline puts the finished standard around 2030 or later. For theme work I can live with that, because if browsers change the math later, the declaration stays the same. It also means the platform keeps taking over the accessibility work that used to be ours to patch.
What I’d change first
On a client site this week I’d put the @supports block in the stylesheet, cover buttons and sale badges, and leave everything else alone. Then I’d pick the two or three presets clients repaint most often and check those by eye, since black text on a mid tone can pass the math and still be hard to read.