diff --git a/public/og-default.png b/public/og-default.png index 88ec504a..1f7063a3 100644 Binary files a/public/og-default.png and b/public/og-default.png differ diff --git a/public/og/checklist.png b/public/og/checklist.png index 20457ef7..43df92e5 100644 Binary files a/public/og/checklist.png and b/public/og/checklist.png differ diff --git a/public/og/spec.png b/public/og/spec.png index 476638ad..938ffb0e 100644 Binary files a/public/og/spec.png and b/public/og/spec.png differ diff --git a/public/og/spec/accessibility.png b/public/og/spec/accessibility.png index bf6ec858..cc9ef9db 100644 Binary files a/public/og/spec/accessibility.png and b/public/og/spec/accessibility.png differ diff --git a/public/og/spec/accessibility/contrast-color.png b/public/og/spec/accessibility/contrast-color.png new file mode 100644 index 00000000..8409c818 Binary files /dev/null and b/public/og/spec/accessibility/contrast-color.png differ diff --git a/src/content/changelog/2026-07-04-contrast-color.md b/src/content/changelog/2026-07-04-contrast-color.md new file mode 100644 index 00000000..cc60f8f3 --- /dev/null +++ b/src/content/changelog/2026-07-04-contrast-color.md @@ -0,0 +1,8 @@ +--- +title: New page on automatic contrasting colour +date: "2026-07-04" +type: added +relatedSlugs: [contrast-color] +--- + +Added a page on [automatic contrasting colour](/spec/accessibility/contrast-color/) — the CSS `contrast-color()` function that returns a legible black or white foreground for any background, so a dynamic or user-picked colour keeps readable text without hard-coded colour pairs or luminance maths in JavaScript. Documented as `recommended`: it became newly available across major browsers in April 2026 and degrades safely, but a site works fine computing contrast by hand, so it is not `required`. diff --git a/src/content/spec/accessibility/color-contrast.md b/src/content/spec/accessibility/color-contrast.md index aa5b6df9..454c012f 100644 --- a/src/content/spec/accessibility/color-contrast.md +++ b/src/content/spec/accessibility/color-contrast.md @@ -6,7 +6,7 @@ summary: "Text and meaningful non-text elements must have enough contrast agains status: required order: 10 appliesTo: [all] -relatedSlugs: [focus-indicators, semantic-html, forced-colors] +relatedSlugs: [focus-indicators, semantic-html, forced-colors, contrast-color] updated: "2026-05-29T09:13:20.000Z" sources: - title: "WCAG 1.4.3 — Contrast (Minimum) (Level AA)" diff --git a/src/content/spec/accessibility/contrast-color.md b/src/content/spec/accessibility/contrast-color.md new file mode 100644 index 00000000..835a0586 --- /dev/null +++ b/src/content/spec/accessibility/contrast-color.md @@ -0,0 +1,75 @@ +--- +title: "Automatic contrasting colour" +slug: contrast-color +category: accessibility +summary: "Let the browser pick a legible black or white foreground for a dynamic background with the CSS contrast-color() function, instead of hard-coding colour pairs or computing luminance in JavaScript." +status: recommended +order: 12 +appliesTo: [all] +relatedSlugs: [color-contrast, color-scheme, forced-colors] +updated: "2026-07-04T00:00:00.000Z" +sources: + - title: "CSS Color Module Level 5 — contrast-color()" + url: "https://drafts.csswg.org/css-color-5/#contrast-color" + publisher: "W3C CSS Working Group" + - title: "WCAG 1.4.3 — Contrast (Minimum) (Level AA)" + url: "https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html" + publisher: "W3C" + - title: "MDN — contrast-color()" + url: "https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/color_value/contrast-color" + publisher: "MDN" + - title: "How to have the browser pick a contrasting color in CSS" + url: "https://webkit.org/blog/16929/contrast-color/" + publisher: "WebKit" +--- + +## What it is + +`contrast-color()` is a CSS function that takes a `` and returns whichever of `black` or `white` contrasts more strongly with it. You pass it the background (or the text colour) and the browser hands back a legible foreground, so the pairing is never hard-coded. + +```css +button { + background-color: var(--accent); + color: contrast-color(var(--accent)); +} +``` + +It is defined in CSS Color Module Level 5. The function commonly targets the WCAG AA minimum of 4.5:1, and browsers are free to use a more perceptually accurate algorithm to choose between black and white. + +## Why it matters + +- **One source of truth for colour.** When a background is dynamic — a user-chosen accent, a brand colour set in a CMS, a value that flips with `prefers-color-scheme` — you cannot know the right foreground ahead of time. `contrast-color()` computes it at render, so text stays readable whatever the background becomes. +- **No JavaScript.** It replaces the familiar helper that parses a colour, computes relative luminance, and picks black or white — one declaration instead of a script that has to run on every theme change. +- **It degrades safely.** This is progressive enhancement: browsers that do not understand it ignore the declaration, so pair it with a sensible fallback rather than relying on it. + +## How to implement + +Feed the same custom property to both the background and `contrast-color()`, and provide a hand-picked fallback colour first so unsupporting browsers still get legible text: + +```css +button { + background-color: var(--accent); + color: #fff; /* fallback for older browsers */ +} + +@supports (color: contrast-color(red)) { + button { + color: contrast-color(var(--accent)); + } +} +``` + +Because the result is only ever black or white, reserve it for backgrounds that are clearly light or clearly dark, where one of the two is a strong match. + +## Common mistakes + +- **Mid-tone backgrounds.** Against a mid-tone such as a royal blue (`#2277d3`), neither black nor white reliably clears AA for small text. The function still returns one of them; it does not guarantee a readable pair. Keep input colours light or dark, or verify the result. +- **Expecting a brand colour back.** It returns `black` or `white` only — never a tint of your palette. +- **Shipping it with no fallback.** Without a plain `color` declaration first, older browsers fall back to the default text colour, which may be illegible on your background. + +## Verification + +- Feature-query it: `@supports (color: contrast-color(red))` tells you whether the browser will honour it. +- Inspect the computed `color` in DevTools and confirm the resulting pair meets 4.5:1 against the actual background values you ship, using a contrast checker. +- Toggle dark mode or change the dynamic colour and confirm the text stays legible either way. +- Baseline: `contrast-color()` became newly available across major browsers in April 2026 (Safari 26, Firefox 146, Chrome and Edge 147). Treat it as an enhancement for everyone else. diff --git a/src/content/spec/accessibility/forced-colors.md b/src/content/spec/accessibility/forced-colors.md index 6c8274d3..ba5cd3e9 100644 --- a/src/content/spec/accessibility/forced-colors.md +++ b/src/content/spec/accessibility/forced-colors.md @@ -6,7 +6,7 @@ summary: "Respect forced colours mode (Windows High Contrast and similar). The ` status: recommended order: 15 appliesTo: [all] -relatedSlugs: [color-contrast, color-scheme, reduced-motion] +relatedSlugs: [color-contrast, color-scheme, reduced-motion, contrast-color] updated: "2026-06-22T00:00:00.000Z" sources: - title: "CSS Media Queries Level 5 — forced-colors" diff --git a/src/content/spec/foundations/color-scheme.md b/src/content/spec/foundations/color-scheme.md index eef1b5bc..dbb62c86 100644 --- a/src/content/spec/foundations/color-scheme.md +++ b/src/content/spec/foundations/color-scheme.md @@ -6,7 +6,7 @@ summary: "Tells the browser which colour schemes your page is designed for. Prev status: recommended order: 95 appliesTo: [all] -relatedSlugs: [theme-color, favicons, pwa-manifest, forced-colors, text-wrap] +relatedSlugs: [theme-color, favicons, pwa-manifest, forced-colors, text-wrap, contrast-color] updated: "2026-06-30T00:00:00.000Z" sources: - title: "HTML Living Standard — Standard metadata names: color-scheme"