1. Presentation
  2. Color
  3. Color for people

Presentation · Color 08

Color for people

Three rules cover most of what color owes its readers: enough contrast, never color alone, and let the reader's settings win. This page measures the first, shows what the second looks like in a real store, and spends the rest on the people the rules are for: readers with a color vision deficiency, readers with low vision or older eyes, and readers who replace your colors with their own.

Page 01, Why color matters, gave the numbers: low-contrast text is the most commonly detected accessibility failure on the web, found on 83.9 percent of the top million home pages in 2026. It also introduced the three WCAG success criteria about color and the habit of redundancy. Page 02 explained relative luminance, the quantity contrast is built on. This page is the full treatment: the thresholds and how to measure them, what the formula misses, and how a page survives when the reader's eyes or settings change every color on it. Three demos let you measure, see and break things, and the Terra Outfitters stores supply the failures.

1. Three rules

Every section below comes back to one of these:

  1. Enough contrast. Text needs at least 4.5:1 against its background, large text and the parts of controls and graphics at least 3:1. These are WCAG 2.2's level AA numbers, the level that laws, contracts and audits usually ask for.
  2. Never color alone. Anything color says, a word, an icon, a shape, a pattern or a position also says.
  3. The reader's settings win. Forced colors, a contrast preference, a user style sheet, grayscale or a browser's color override may replace your palette. A page built from real structure keeps working when they do.

The rules are not a style. They leave room for any palette, as the store's fixed Trail report shows: it keeps its orange brand, its lake photo and its two-color chart, and passes all three. What they rule out is a page whose meaning depends on the reader seeing the colors exactly as the designer did, which no page can promise.

2. Contrast, measured

WCAG 2's contrast ratio compares the relative luminance of two colors, how much light each sends to the eye, as (L1 + 0.05) / (L2 + 0.05), with the lighter color on top. The result runs from 1:1, two identical colors, to 21:1, black on white. Page 02 shows the arithmetic. Three success criteria use the ratio:

The WCAG 2.2 contrast criteria
CriterionLevelNormal textLarge textControls and graphics
1.4.3 Contrast (Minimum)AA4.5:13:1n/a
1.4.6 Contrast (Enhanced)AAA7:14.5:1n/a
1.4.11 Non-text ContrastAAn/an/a3:1 against adjacent colors
The contrast scale from 1:1 to 21:1, with the WCAG thresholds markedA horizontal scale from 1:1 on the left to 21:1 on the right, drawn on a logarithmic axis. Marks at 3:1 (large text and non-text, AA), 4.5:1 (normal text AA, large text AAA) and 7:1 (normal text AAA). Below the scale, five gray swatches on white, each labeled with its hex value and its ratio: #EEEEEE at 1.16:1, #949494 at 3.03:1, #767676 at 4.54:1, #595959 at 7.00:1 and black at 21:1. The swatches sit under the positions of their ratios.1:121:13:1large text, UI parts4.5:1 body text AA7:1body text AAA#EEEEEE1.16:1#9494943.03:1#7676764.54:1#5959597.00:1black21:1Each gray measured against white, on a logarithmic axis.
The thresholds on one scale. #767676 is the lightest gray that passes 4.5:1 on white; #595959 is the lightest that passes 7:1.

The details matter when a pair sits near a line:

  • Large text is at least 18 point, or 14 point bold. The Understanding document converts those to about 24 and 18.5 CSS pixels; many tools say 18.66, which is 14 times 4/3. Below that, use the normal-text number. Type for people explains why thin and light fonts need more than the minimum.
  • No rounding. WCAG's Understanding document says computed ratios "should not be rounded (e.g., 4.499:1 would not meet the 4.5:1 threshold)". Tools that print one decimal can show 4.5 for a pair that fails.
  • Exempt: logotypes, purely decorative text, text in pictures that is incidental, and inactive components such as disabled buttons. Exempt is not a recommendation: a disabled button nobody can read leaves people wondering what they cannot do.
  • 1.4.11 asks about what identifies a control. A text field's border, a checkbox's box, a slider's thumb, a chart's lines and an icon that is the only label all need 3:1 against what is next to them. A control does not need a boundary at all if its text or icon already shows it is there; the Understanding document says a visible hit area "is only required when there is no other visual way to identify the presence of the control". States count too: a checked box must look checked at 3:1.

The broken stores supply examples. The inaccessible store's SIN #7a sets body text in #9a9a9a on a #f6f4ee page: 2.56:1, which fails even the large-text minimum. The discolored Trail report's custom checkboxes (SIN #6) are light gray squares on a white panel, 1.28:1, so unchecked boxes are close to invisible to many readers before forced colors ever arrive.

Translucent colors are measured as painted

A text color of oklch(0% 0 0 / 50%) is a different gray on every background, so its contrast cannot be read off the token. Measure what is painted: blend the color over what is behind it, then compute the ratio of the result. The section's shared helper does exactly that and refuses to do anything else: its contrastRatio() returns null for a translucent color, and composite() must lay it over an opaque background first. The same rule applies to a translucent background, which is a different color over every pixel of what lies beneath. WCAG 3's draft says the same thing in a note: transparency "should be tested as the rendered color".

Every pair states its contrast

The contrast check is arithmetic, so it belongs in a test, not only in a designer's checklist. That is this section's second house rule, and it is how this site checks its own theme. test/theme-contrast.test.js reads the oklch() values straight out of powell-theme.css, converts each to sRGB with scripts/color/oklch.mjs, and asserts the ratios the theme depends on:

const theme = readFileSync('src/assets/css/powell-theme.css', 'utf8'); // Every color is oklch(); ratios are computed from the oklch() value itself. const hex = (name) => oklchToHex(parseOklch( theme.match(new RegExp(`--${name}: (oklch\\([^)]*\\))`))?.[1])); test('accent text passes WCAG AA for normal text on the light and dark code backgrounds', () => { const [, light, dark] = theme.match( /--color-accent-text: light-dark\(var\(--([\w-]+)\), var\(--([\w-]+)\)\);/); assert.ok(ratio(hex(light), '#FFFFFF') >= 4.5); assert.ok(ratio(hex(light), hex('gray-50')) >= 4.5); assert.ok(ratio(hex(dark), hex('navy-900')) >= 4.5); });

The test runs with every other test, so a designer who lightens a navy or a developer who swaps a token finds out from a failing build, not from a reader. Two habits make this work for any project. Compute from the values the browser uses, not from a hex value in a comment that can go stale. And test pairs, not colors: a color has no contrast, only a text color on a particular background does, so the test names the roles that meet (--color-accent-text on the code background) rather than checking a swatch alone. Page 03, Color in CSS, tells how the theme was converted.

3. What the formula misses, and APCA

WCAG 2's formula is simple, well tested and legally entrenched, and it has known gaps:

  • It ignores polarity. Swap text and background and the ratio is identical, but dark text on a light page and light text on a dark page are not equally easy to read, which matters for dark themes.
  • It ignores weight and size beyond the two bins of normal and large. A 300-weight face at 14 pixels and a 700-weight face at 20 pixels get the same verdict for the same colors.
  • It misjudges mid-tones and light-on-dark pairs. On the store's orange, white text measures 5.13:1 and black 4.10:1, so the formula prefers white; on a mid blue such as oklch(60% 0.15 250) it prefers black, at 5.4:1 against white's 3.9:1, although many readers find white easier there. Page 04's section on contrast-color() works through that case.
  • It knows nothing about surroundings: the size of the area, the light around the screen, or the thin anti-aliased edges of small text.

The Accessible Perceptual Contrast Algorithm, APCA, by Andrew Somers, models these. It treats the two polarities differently, reports a lightness contrast value (Lc) rather than a ratio, and ties its thresholds to font size and weight through lookup tables. Early drafts of WCAG 3 used it. It was removed from the working draft published on 24 July 2023, under the working group's rule that exploratory content without group support is dropped after six months. The current Working Draft, dated 10 September 2026, keeps a placeholder for text contrast and an editor's note: "The contrast algorithm used in WCAG 3 is yet to be determined." Adrian Roselli's April 2026 summary of the history says that nobody can yet say whether APCA, in any version, will return.

So the practice is plain. Meet WCAG 2.2's numbers, because they are what audits and laws measure. Use APCA, or simply your eyes on a real screen in a bright room, as a second opinion, especially for dark themes, thin fonts and mid-tone backgrounds. Where the two disagree, a pair that satisfies both is usually available a few steps of lightness away.

4. Text over images

Text on a photograph has no single background, so a single ratio means nothing. WCAG measures contrast against "the specified background over which the text is rendered", and for a photo that is every pixel behind the letters. Check the worst pixel, not the average, because the reader's eye stops at the letter that disappears.

The discolored Trail report's SIN #7 sets a white headline straight on a photo of a mountain lake. Measured pixel by pixel across the top-left region where the words sit, white against that pale evening sky ranges from 1.74:1 to 2.85:1, with a median of 2.45:1. Every pixel fails 3:1, the large-text minimum. It looked fine in the mockup, which the source comment says used a darker stock photo; this is how text over images usually fails, when content changes and the style sheet does not.

The fixed page's COLOR FIX #7 puts the words on a band, --scrim: oklch(22% 0.03 150 / 0.85). The band is translucent, so the rule above applies: measure it as painted. Over the worst possible pixel, pure white, it paints as #344136, and white text on that measures 10.74:1. Any darker pixel only makes the band darker, so 10.74:1 is a floor for the whole photo. The techniques, in order of reliability:

  • A solid or nearly solid band behind the text, as the store uses. It works on any image and is easy to verify.
  • A gradient scrim over the part of the image where the text sits. Check the ratio at the gradient's weakest point under the text.
  • Choose and crop the image so the text sits on a calm, dark or light area, and recheck whenever the image changes. That is the fragile one.

A text-shadow halo helps on screen, but forced colors remove text shadows entirely, so it cannot be the only defense. Forced colors bring their own help instead: the browser may paint a solid backplate behind text, which is why the hero text in the screenshots of section 12 stays readable. Text baked into an image is a different problem, covered in Type for people.

5. Never color alone

Success criterion 1.4.1, Use of Color, is level A, the floor: "Color is not used as the only visual means of conveying information, indicating an action, prompting a response, or distinguishing a visual element." Page 01 built the habit of redundancy with the inaccessible store's stock dot (SIN #8). The discolored Trail report breaks the rule three more ways, each fixed on the www copy:

  • Links by color alone (SIN #9). The link green, #2f6b3f, is 5.79:1 against the page, readable, but only 2.34:1 against the body text around it, with no underline. The criterion's sufficient technique G183, "Using a contrast ratio of at least 3:1 to distinguish inline text links from surrounding text", sets that 3:1 as the bar this green misses; the simpler answer is the underline, which COLOR FIX #9 restores. Page 02 shows that even the default link blue reaches only 2.2:1 against black text: the convention always relied on the underline. Links people can use covers when an underline can go.
  • Errors only by a red border (SIN #8). Submit the form empty and two fields turn red, with no word anywhere about what is wrong. A reader who cannot tell red from gray sees nothing; a screen reader hears nothing; forced colors replace the red. COLOR FIX #8 writes "Error: enter an email address, like name@example.com." next to the field, after a warning icon, tied to it with aria-describedby, in a red that reaches 6.63:1, and thickens the border as the third cue. Forms people can use covers the messages themselves.
  • A chart told apart by red and green (SIN #5). Two lines with the same width and the same round markers, named only by a legend of colored squares. COLOR FIX #5 labels each line at its end, dashes one, and gives each its own marker shape. Page 05's palettes for data chooses the colors; this rule says the colors must not be the only difference.
<label for="email">Email</label> <input type="email" id="email" name="email" required aria-invalid="true" aria-describedby="email-hint email-error"> <span id="email-hint">One email a week.</span> <span id="email-error" class="error">Error: enter an email address, like name@example.com.</span>

Required fields follow the same rule: a red asterisk is a color cue and a symbol, and the symbol needs a key or, better, the word "required". Status colors, selected states and current-page markers all need a second carrier, which page 05 covers under status colors. The quick test is the one page 01 suggested: look at the page in grayscale. The next section explains why grayscale is a fair stand-in for some readers and only an approximation for most.

6. Color vision deficiency

Human color vision rests on three kinds of cone cell, most sensitive to long, medium and short wavelengths, loosely called red, green and blue. A color vision deficiency is a change in one of them. About 1 in 12 men and 1 in 200 women have one, according to the UK charity Colour Blind Awareness; most inherit it, and the genes for the red-green types sit on the X chromosome, which is why men are affected far more often. In a lecture hall of a hundred men and a hundred women, expect eight or nine.

The main types of color vision deficiency, with prevalence from Colour Blind Awareness
TypeCone affectedColors confusedHow common
Protan (protanomaly, protanopia)Long, "red"Red with green, brown and dark gray; reds look darkerAbout 2 percent of men
Deutan (deuteranomaly, deuteranopia)Medium, "green"Red with green, brown and orangeAbout 6 percent of men; deuteranomaly alone about 5
Tritan (tritanomaly, tritanopia)Short, "blue"Blue with green, yellow with pink or grayVery rare
AchromatopsiaNo working cones, or only one typeAll hues; only lightness remainsAbout 1 person in 33,000

The "-anomaly" forms shift a cone's sensitivity; the "-opia" forms, in which a cone type is missing, are less common overall, mostly because deuteranomaly alone accounts for about 5 percent of men, while protanomaly and protanopia are about 1 percent each. So most affected readers see some difference between red and green, just much less of it, and it shrinks further in dim light, on a small mark, or when the two colors also have similar lightness. That last factor is the one a designer controls. In the diagram, the broken store's red and green chart lines become two olive tones only 1.15:1 apart in luminance under a deutan simulation; the fixed store's rain and sun lines stay apart in hue for red-green readers, and in every view they also differ in dash and marker.

Three color pairs from the Trail report, as designed and through four simulationsFive rows: as designed, then protan, deutan and tritan, simulated with the Machado 2009 matrices at full severity, and achromatopsia, simulated as relative luminance. Three columns of swatch pairs. First pair, the fixed store's up and down price colors, green oklch(48% 0.13 150) and red oklch(52% 0.19 27): as designed they differ in hue only, 1.01 to 1 in luminance; protan and deutan turn both into olive browns; tritan turns them teal and red; achromatopsia turns both into nearly the same mid gray. Second pair, the broken chart's red #d32f2f and green #2e9e3e: protan and deutan turn both into olive; achromatopsia leaves two grays. Third pair, the fixed chart's rain blue and sun orange: protan and deutan keep a blue and an olive yellow apart; tritan turns them teal and pink; achromatopsia leaves two grays. The fixed chart also has dashes, marker shapes and labels, which survive every row.up / down pricesbroken chartfixed chartgreen, redred, greenrain blue, sun orangeAs designedProtanDeutanTritanAchromatopsia
Protan, deutan and tritan simulated with Machado, Oliveira and Fernandes (2009) at full severity, and achromatopsia as relative luminance, in linear RGB: approximations, not anyone's actual sight. Hue differences collapse; lightness differences survive.

The up and down price colors are the instructive case. As designed they are a strong green and a strong red, and they differ in luminance by almost nothing, 1.01:1. That is fine on the fixed page, because the arrow and the word "up" or "down" carry the meaning and the color only reinforces it. It would be a failure anywhere the color stood alone. Two habits follow from the diagram. When two colors must be told apart, separate them in lightness as well as hue, and when the difference matters, add a shape, a pattern or a word that needs no color at all.

Simulate instead of guessing. Chrome and Edge DevTools have "Emulate vision deficiencies" in the Rendering panel, with protanopia, deuteranopia, tritanopia and achromatopsia (plus blurred vision and reduced contrast), and Firefox's Accessibility inspector has a Simulate menu. macOS users can try Sim Daltonism. All of them use matrices like the ones in the diagram, so all of them are approximations: real color vision varies from person to person, and a simulation of the complete "-opia" forms exaggerates what most affected readers see.

7. Low vision and age

WCAG's numbers come from vision science, not taste. The Understanding document for 1.4.3 starts from 3:1, the minimum that ISO and ANSI standards recommend for readers with normal vision, then multiplies by the loss of contrast sensitivity that comes with reduced acuity. Visual acuity of 20/40 goes with a loss of about 1.5, and "a user with 20/40 would thus require a contrast ratio of 3 * 1.5 = 4.5 to 1". The same reasoning for 20/80 gives about 7:1, the AAA number. And the document notes that 20/40 is "commonly reported as typical visual acuity of elders at roughly age 80".

So 4.5:1 is not a generous margin. It is the minimum for an ordinary older reader, before the room and the screen take their share. Several changes come with age: the lens yellows and scatters more light, so blues darken and glare grows; the pupil shrinks, so less light arrives; and contrast sensitivity falls, so low-contrast gray text fades first. Add a phone in sunlight or a washed-out lecture projector, which page 07, Displays and environments, measures, and a pair at 4.6:1 is the first to go.

Three habits follow:

  • Treat the minimum as a floor. Body text well above 4.5:1, around 7:1 where the design allows, costs little and helps many. The fixed Trail report's body text is 13.55:1.
  • Watch the quiet colors. Placeholder text, captions, disabled-looking hints, footers and "muted" secondary text are where most failures live, because they were designed to recede.
  • Survive zoom and enlarged text. Low-vision readers magnify. Contrast that relies on a hairline border, or text that only fits at the designed size, fails them twice. CSS for people covers zoom and reflow.

8. Forced colors

In a forced colors mode the operating system or browser replaces the page's colors with a small palette the reader chose. On Windows 11 it is a contrast theme (Settings › Accessibility › Contrast themes, with themes named Aquatic, Desert, Dusk and Night sky, or Left Alt + Left Shift + Print Screen). Firefox can do the same on any platform when its color settings override the page's colors. macOS and iOS have no forced colors mode; their Increase contrast setting is a preference, the subject of the next section. Readers use forced colors for low vision, for light sensitivity and migraine, and for reading comfort. The CSS Color Adjustment specification (a Candidate Recommendation, December 2025) defines what happens:

  • Colors are forced. color, background-color, border-color, outline-color, text-decoration-color, column-rule-color and others take system colors from the reader's palette. The specification lists SVG fill and stroke too, but it also gives every <svg> forced-color-adjust: preserve-parent-color, so in practice SVG colors are kept; see below. The alpha of a background is kept, so a transparent background stays transparent.
  • Decoration goes. box-shadow and text-shadow compute to none, and background-image computes to none unless it is a url() image, so gradients vanish and photos stay.
  • The browser may help. It may paint a backplate behind text, color-scheme becomes light dark, and accent-color and scrollbar-color become auto.

Chrome 156 shows all of this when you emulate forced colors: an element with a red box-shadow, a text-shadow and a linear-gradient() background reports none for all three, a half-transparent background becomes half-transparent white, and an outline declared transparent computes to black, the palette's text color. What it does not force is SVG, and the specification says it should not: its default style sheet gives inline <svg> forced-color-adjust: preserve-parent-color, and Chrome follows it, so a red <rect> stays red under forced colors whether its fill comes from a fill attribute, a class or a style attribute, possibly on a background now black. Only fill: currentColor, inheriting the forced text color from the HTML around the SVG, follows the reader's palette. So draw icons and marks with currentColor, as page 03 recommended under currentColor, or set system colors for them in a forced-colors rule, as the fixed store does for its chart.

The system colors

Under forced colors the palette is exposed to CSS as system colors, the same keywords page 03 introduced. They come in pairs meant to be used together:

System colors, in pairs
BackgroundForegroundFor
CanvasCanvasTextThe page and its text
CanvasLinkText, VisitedText, ActiveTextLinks
ButtonFaceButtonText, with ButtonBorderButtons
FieldFieldTextText inputs
HighlightHighlightTextSelected text and selected items
SelectedItemSelectedItemTextSelected items in lists and menus
MarkMarkTextHighlighted passages
CanvasGrayTextDisabled text

Use a pair, never half of one. Highlight text on Canvas may have any contrast at all, while HighlightText on Highlight is the pair the reader's theme guarantees.

What disappears, and the fixes

Anything drawn only with a background color disappears, because every background becomes the same Canvas or ButtonFace. That is the discolored Trail report's SIN #6: checkboxes made with appearance: none and a background that turns from gray to orange, with no border and no check mark. Under forced colors the boxes, checked or not, are gone, the "Sign up" button loses its edge, and the red error borders of SIN #8 become the same color as every other border.

The discolored store's sign-up form under forced colors, after submitting it empty. Under Send me, the three labels Trail conditions, Sale alerts and New gear have no checkboxes beside them, nothing marks an error, and Sign up is plain text with no button edge.
discolored, under forced colors (Chrome 156, emulated) after an empty submit: the custom checkboxes and the error borders are gone.
The fixed store's sign-up form in the same conditions. Three checkboxes are drawn with borders, both errors appear in words after a warning sign, the email field has a thick focus border, and Sign up has a button edge.
www, the same conditions: native checkboxes, errors in words, a button with an edge.

The fixes are few and small, and most of them are good practice anyway:

  • Real controls. COLOR FIX #6 uses native checkboxes tinted with accent-color. The browser draws the box and the check, so they survive forced colors, zoom and every theme.
  • Borders and outlines, even invisible ones. A button with border: 0.125rem solid transparent looks borderless on screen; under forced colors the border takes the button text color and the button has an edge again. The same trick works for focus: an outline survives where a box-shadow ring is removed.
  • currentColor for icons and marks, so they follow the forced text color.
  • A small @media (forced-colors: active) patch for what remains, using system color pairs. Keep it small: the reader chose their palette, and the patch exists to restore structure, not to restore your brand.
  • forced-color-adjust: none only where the color is the content, such as a color picker's swatches or a product's color options, and then with a text label, because the reader may not be able to see the swatch.
@media (forced-colors: active) { :focus-visible { outline-color: Highlight; } .button { border-color: ButtonText; } .hero .hero-text, .panel { border: 0.0625rem solid CanvasText; } /* The chart keeps its dashes and markers, in two system colors. */ .chart .rain { stroke: CanvasText; fill: CanvasText; } .chart .sun { stroke: LinkText; fill: LinkText; } [data-trend] { color: CanvasText; } }
What forced colors keep, change and removeThree columns. Kept: text and its size, weight and font; borders and outlines, including transparent ones, which become visible; url() images such as photographs; layout. Changed to the reader's system colors: text color, background color with its alpha kept, border and outline colors, and link colors. SVG fill and stroke are listed by the specification, but its rule for svg elements keeps them, so only currentColor follows the palette there. Removed: box-shadow, text-shadow, and background images that are not url() images, such as gradients. Below: a control drawn only with background color disappears; a control with a border, an outline and currentColor survives.Kepttext, size, weight, fontborders and outlines,even transparent onesurl() images: photoslayout and spacingChanged to system colorscolor, background-color(background alpha kept)border and outline colorsSVG fill, stroke: kept by thespec's svg rule (use currentColor)links: LinkTextRemovedbox-shadowtext-shadowgradients and othernon-url() background imagesDrawn only with background color: disappears.Drawn with a border, an outline and currentColor: survives.
Forced colors keep structure and replace color. Whatever a page draws with color alone, it loses.

Support is broad. The forced-colors media feature works in Chrome and Edge, Firefox and Safari, and has been Baseline widely available since March 2025; Safari parses it, but since Apple's systems have no forced colors mode it never matches there. forced-color-adjust works in Chromium browsers and in Firefox from 113, but not in Safari. The older, Microsoft-only -ms-high-contrast media query worked in Internet Explorer and in Edge until version 138, in 2025; use the standard one.

9. prefers-contrast and other preferences

Forced colors replace the palette. A preference only asks, and leaves the answer to the page. The prefers-contrast media feature has four values: no-preference; more, which on macOS and iOS follows the Increase contrast setting; less, for readers who find strong contrast uncomfortable; and custom, meant to match a forced-colors palette that is neither clearly more nor clearly less contrasty. It has been Baseline widely available since November 2024 (Chrome 96, Firefox 101, Safari 14.1).

Answer more by strengthening what is already there, through tokens: darker muted text, solid borders where there were faint ones, thicker focus rings and underlines, and no text over images. Answer less by softening the extremes, such as pure black on pure white. Neither replaces meeting the minimum, because most readers who need contrast never set the preference, and some cannot.

@media (prefers-contrast: more) { :root { --text-muted: var(--text); --line: var(--text); --focus-width: 0.25rem; } a { text-decoration-thickness: 0.15em; } .hero .hero-text { background: var(--surface); color: var(--text); } } @media (prefers-contrast: less) { :root { --text: light-dark(oklch(30% 0.02 145), oklch(88% 0.01 145)); } }

Do not assume the values combine predictably. In Firefox 150, the build Playwright drives, tested on 27 October 2026 with its color override turned on, forced-colors: active matched and none of more, less or custom did. Write each response so it stands on its own.

prefers-reduced-transparency: reduce reports the system setting that turns translucent panels and frosted-glass effects opaque, often used by readers for whom a busy background behind text makes it hard to read. It ships only in Chromium browsers, from version 118; Firefox has it behind a flag and Safari does not have it, so it is not Baseline. Treat it as a refinement: make translucent surfaces readable without it, then make them solid when it matches.

.toolbar { background: oklch(98% 0.01 90 / 0.75); backdrop-filter: blur(0.75rem); } @media (prefers-reduced-transparency: reduce) { .toolbar { background: oklch(98% 0.01 90); backdrop-filter: none; } }

Responsive design introduced these features beside prefers-color-scheme and prefers-reduced-motion; page 06, Light and dark, covers the color scheme in full.

10. Focus you can see

For a keyboard user the focus indicator is the cursor. Three WCAG 2.2 criteria govern it:

  • 2.4.7 Focus Visible (AA): some indicator is visible. Removing the browser's ring with nothing in its place fails it, which is the inaccessible store's SIN #4a, the single rule *:focus { outline: none; }.
  • 2.4.11 Focus Not Obscured (Minimum) (AA), new in 2.2: the focused element is not entirely hidden by content the author added, such as a sticky header or a cookie banner. Links people can use shows the scroll-padding fix.
  • 2.4.13 Focus Appearance (AAA), also new: when the indicator is visible, an area of it "is at least as large as the area of a 2 CSS pixel thick perimeter of the unfocused component or sub-component", and "has a contrast ratio of at least 3:1 between the same pixels in the focused and unfocused states". It is level AAA, but it is the one criterion that turns "visible" into numbers, so it is a good target for a design system.

An author's own indicator also falls under 1.4.11: it needs 3:1 against what is next to it. The difficulty is that one ring color rarely works everywhere. A dark navy ring is 16.1:1 on white and 1.3:1 on black; white is the reverse. The answer is a two-tone ring, which WCAG lists as technique C40: an outline in one color with a contrasting color right beside it, so that whatever the background, one of the two meets 3:1. With navy oklch(25% 0.05 260) and white, the store's orange gets 3.14:1 from the navy and 5.13:1 from the white, and a mid gray gets 4.08:1 and 3.95:1.

:root { --focus-ring: oklch(25% 0.05 260); --focus-halo: oklch(100% 0 0); --focus-width: 0.1875rem; } :focus-visible { outline: var(--focus-width) solid var(--focus-ring); outline-offset: 0.125rem; /* The second tone; removed under forced colors, where the outline remains. */ box-shadow: 0 0 0 calc(0.125rem + var(--focus-width) + 0.1875rem) var(--focus-halo); } @media (forced-colors: active) { :focus-visible { outline-color: Highlight; } }

The shadow is the second tone and the outline is the first, which matters for forced colors: the shadow is removed and the outline, recolored to the reader's Highlight, remains. A ring made only of box-shadow disappears there. CSS for people introduced :focus-visible; this is the color half of the same job.

11. Asking from a script, and measuring in aggregate

CSS answers these preferences with no code, and should. A script needs to know only when it draws something itself, such as a canvas chart in the reader's colors, or when it measures. matchMedia() asks the same questions, and a change listener catches a reader switching on a contrast theme while the page is open:

const forced = matchMedia('(forced-colors: active)'); const more = matchMedia('(prefers-contrast: more)'); const solid = matchMedia('(prefers-reduced-transparency: reduce)'); // A browser that does not know a feature reports its media as "not all". const known = (q) => q.media !== 'not all'; function redraw() { chart.draw({ forced: forced.matches, // draw with system colors strong: more.matches, // thicker lines, no pale fills solid: known(solid) && solid.matches, }); } for (const q of [forced, more, solid]) q.addEventListener('change', redraw); redraw();
Detecting the reader's color settings, October 2026
QuestionCSS and JavaScriptSupport
Forced colors on?(forced-colors: active)Baseline widely available (March 2025); never matches on Apple systems
Keep an element's own colors?forced-color-adjust: noneChromium, Firefox 113; not Safari
More or less contrast?(prefers-contrast: more), less, customBaseline widely available (November 2024)
Less transparency?(prefers-reduced-transparency: reduce)Chromium 118 only; Firefox behind a flag

It is tempting to measure how many readers use each setting, and it is worth knowing: a site that finds a few percent of visits under forced colors has a reason to test that way. But each answer is also a fingerprinting bit. Forced colors, a contrast preference, reduced transparency, the color scheme and the gamut together split visitors into dozens of groups, and combined with the screen size, the language and the time zone they help identify one person across sites. Some of them are also health information: a contrast theme is often a sign of low vision.

So measure in aggregate, never as profiles. Count each setting as a separate tally with no identifier attached, do not join it to a user account or to the other answers, and keep only the totals. That is enough to decide what to test, and it collects nothing a reader would mind seeing. Privacy sets out the principles; this is one of their small, concrete cases.

12. Your colors, not ours

The reader can and will replace your palette. Here is the fixed Trail report, the same page, five ways. Each of these is something a real reader does, and none of them asks the author's permission.

The fixed Trail report as designed, in light mode: a dark green header, the lake photo with the headline Trail report: spring is here on a dark translucent band, an orange Get the report button, a blue-to-orange gradient bar, olive Sun gear and blue Rain gear buttons, and a white panel of price changes with green up and red down.
1. As designed, light mode (Chrome 156).
The same page under forced colors with a dark palette: black background, white text, yellow links, the photo unchanged, the headline on a black backplate, buttons as outlined boxes with yellow text, the gradient bar now solid white, and the price changes in white with their arrows and words.
2. Forced colors with a dark palette (Chrome 156, emulated, as a Windows contrast theme would apply it).
The same page with Firefox's color override: white background, black text, blue underlined links, the headline on a white backplate inside a bordered box, buttons with blue text and black borders, a solid black bar where the gradient was, and the price panel in black and white.
3. Firefox's own color override ("Override the colors specified by the page", default colors), Firefox 150 as driven by Playwright, 27 October 2026.
The same page in grayscale: every color is a gray, the two sale buttons are mid grays of similar lightness, and the up and down prices are told apart by their arrows and words.
4. Grayscale, applied with filter: grayscale(1) on the root, as the system grayscale setting would show it (Chrome 156).
The same page under a reader's style sheet: a serif font throughout, pale yellow text on deep navy everywhere including the header and panels, light blue underlined links, wider line spacing, and the photo unchanged.
5. A user style sheet of the kind readers add with a browser extension: their font, their colors, more line spacing (added here as a style element, Chrome 156).

Every view keeps its meaning, and for the same reasons. The headline is readable in all five: it sits on its band as designed, in grayscale and under the user style sheet, and on the browser's backplate under forced colors and the override. Up and down are words and arrows, so they survive grayscale and every palette. The links are underlined, so they stay links when their color is someone else's. The buttons have borders, invisible in the design, that appear when they are needed. The gradient bar loses its colors in three views and nothing is lost, because it was decoration.

The discolored copy shows the opposite: its custom checkboxes vanish under forced colors, its errors exist only as a border color, and its links are found only by a green that a user style sheet replaces with the color of everything else. The lesson of this whole section, from tokens to gamuts to contrast, ends here. Design the colors carefully, and build the page so that it does not need them.

13. Further reading

14. Summary

Color for people, in brief
ConceptKey points
Three rulesEnough contrast, never color alone, and the reader's settings win.
Contrast, measuredWCAG 2 ratio of relative luminance, 1:1 to 21:1. 1.4.3 AA: 4.5:1 text, 3:1 large text (18pt, or 14pt bold). 1.4.6 AAA: 7:1 and 4.5:1. 1.4.11: 3:1 for what identifies controls, states and graphics. No rounding; logos and inactive controls exempt.
TranslucencyA translucent color has no ratio of its own; composite it over what is behind it and measure the result.
Every pair states its contrastCompute ratios from the tokens in a test, for pairs of roles, on every run, as this site's theme test does.
The formula's limitsIgnores polarity, weight and surroundings; misjudges mid-tones. APCA models them but left the WCAG 3 draft in July 2023; the 10 September 2026 draft's algorithm is "yet to be determined". Meet WCAG 2, use APCA and your eyes as a second opinion.
Text over imagesMeasure against the worst pixel. A solid or near-solid band is the reliable fix; text shadows vanish in forced colors.
Never color alone1.4.1, level A. Underline links, write errors in words, label chart lines and vary their dash and marker, say "required".
Color vision deficiencyAbout 1 in 12 men, 1 in 200 women; red-green types most common. Separate colors in lightness, add a non-color cue, and simulate in DevTools, knowing simulations are approximations.
Low vision and age4.5:1 is the minimum for 20/40 vision, typical around age 80. Treat it as a floor; watch muted text.
Forced colorsThe reader's palette replaces yours; shadows and gradients go. Use real controls, borders and outlines (even transparent), currentColor, small system-color patches, and forced-color-adjust: none only for color as content.
Preferencesprefers-contrast (Baseline) strengthens or softens tokens; prefers-reduced-transparency (Chromium only) makes glass solid. Neither replaces the minimum.
Focus2.4.7 visible, 2.4.11 not obscured, 2.4.13 (AAA) a 2px perimeter at 3:1. A two-tone ring, outline first, works on any background and in forced colors.
MeasuringmatchMedia() with change listeners. Each preference is a fingerprinting bit: count in aggregate, never as profiles.
Your colors, not oursForced colors, overrides, grayscale and user style sheets replace the palette. A page built from structure keeps its meaning.