Our marketing site ships one stylesheet. It is 86,718 bytes, it has 13 media queries, and every one of them is min-width or hover. Searching it for the things that describe a reader's own display preferences returns nothing at all:
| searched for | occurrences |
|---|---|
forced-colors |
0 |
forced-color-adjust |
0 |
prefers-contrast |
0 |
prefers-reduced-motion |
0 |
@media print |
0 |
Canvas, LinkText, ButtonFace
|
0 |
So when somebody browses our site with a high contrast theme on, nothing in our CSS participates. The browser decides on its own, and what it decides is worth measuring rather than guessing.
Reproduce it in about ten seconds
Open pub-trivia.app, then in Chrome DevTools open the Rendering panel (Command or Control + Shift + P, type "Rendering"), scroll to Emulate CSS media feature forced-colors and set it to active. Everything below is what we measured that way, plus the same run through CDP so the numbers are reproducible.
You can do the same on pub-trivia.app/pricing and on any content page, for example pub-trivia.app/features, and get three quite different results from the same stylesheet.
Every gradient disappears
The homepage has 723 elements under header, main and footer. 61 of them have a background-image that is a gradient:
linear-gradient(to top, rgba(46, 46, 46, 0.4), rgba(0, 0, 0, 0))
linear-gradient(to left, rgba(46, 46, 46, 0.4), rgba(0, 0, 0, 0))
Those are the corner arms and the double-ended dividers we use instead of borders. With forced colors active, all 61 compute to background-image: none. Chrome strips gradient backgrounds in forced colors mode, and there is no negotiating with it from CSS.
/pricing goes from 18 gradient elements to 0. Content pages such as /features have 0 either way, because the gradient decoration only exists on the two pages that are rendered per request.
The hairline grids collapse into one box
We draw the divisions inside a card grid with a one pixel gap over a container whose background is the border colour, so the container shows through as a hairline. Four grids on the homepage do this, two on /pricing:
grid gap-px border sm:grid-cols-3
grid gap-px border sm:grid-cols-2 lg:grid-cols-3
grid gap-px border md:grid-cols-[2fr_3fr]
Normally the container is rgba(46, 46, 46, 0.4) and each child is rgb(23, 23, 23), which is the difference you see as a line. With forced colors on, the container becomes rgba(0, 0, 0, 0.4) and the children become rgb(0, 0, 0). Black over black. The gap is still one pixel wide and it is now invisible.
The outer border on the same element survives, forced to white. So the three-up "how it works" grid stops being three cells and becomes one white rectangle with three paragraphs loose inside it.
This is the cost of having drawn a border with a background colour. A real border would have been forced to a visible system colour for free. Our hairline is not a border as far as the browser is concerned, it is decoration.
Buttons stop looking like buttons
The homepage has 11 links inside main. In normal rendering they fall into three groups:
| group | colour on background | count |
|---|---|---|
| primary call to action |
rgb(23, 23, 23) on rgb(250, 250, 250)
|
4 |
| plain prose link |
rgb(250, 250, 250) on transparent |
4 |
| muted link |
rgb(153, 153, 153) on transparent |
2 |
| inverted |
rgb(250, 250, 250) on rgb(23, 23, 23)
|
1 |
With forced colors active, all 11 are rgb(255, 255, 0), which is Chrome's LinkText in a dark forced palette. Five of them keep a background, and that background is now rgb(0, 0, 0), the same black as the page.
So "Get started" is yellow text on black, and so is a link in the middle of a paragraph. The only thing still distinguishing the main call to action from body copy is its padding. Because we built our buttons out of a background colour rather than a border, the shape goes with the colour.
Our typographic hierarchy is three greys, and it flattens to one
This is the finding that surprised us. /features renders 85 pieces of text under main in three distinct colours:
| colour | role | count |
|---|---|---|
rgb(250, 250, 250) |
headings and body | 44 |
rgb(153, 153, 153) |
muted captions and labels | 26 |
oklab(0.984799 ... / 0.8) |
slash opacity modifier on the foreground | 15 |
Our palette is a pure neutral ramp. There is no accent colour anywhere in the page text. Every bit of hierarchy below heading size is carried by how light a grey is.
In forced colors mode those three become 76 elements of rgb(255, 255, 255) and 9 of rgb(255, 255, 0), the yellow being the links. Three tiers become one. Nothing is unreadable, which is the point of the mode, but a caption and a heading are now the same colour, and only size and weight are left to tell them apart.
That is not a bug we can fix with a media query. It is a design that spent its entire contrast budget on grey.
The decoration survives and the structure does not
The one thing forced colors does not touch on our homepage is the two decorative screenshots. They are CSS background images, and they are 953,385 and 915,548 bytes:
url("https://pub-trivia.app/demo-2.webp")
url("https://pub-trivia.app/demo-3.webp")
Chrome strips gradient backgrounds and leaves bitmap ones alone. So after the mode has removed every divider, flattened every text colour and dissolved every button, the two largest and least necessary assets on the page are still there in full colour.
What we would actually change
Three things, in cost order:
- Put a real
borderon thegap-pxgrid children, or an@media (forced-colors: active)block that adds one. The hairline is the structure of three sections of the homepage and it is the first thing to go. - Give the primary call to action a border so it survives as a shape. It currently reads as a button only because of its background.
- Decide deliberately about
forced-color-adjust: none, which is the escape hatch for a thing that genuinely needs its own colours. We use it nowhere, which is the right default, and that should be a choice rather than an oversight.
What we would not change is the text. The flattening is real but the result is readable, and a reader who has asked for one foreground colour has asked for exactly this.
If you want to see the before and after yourself, the two pages where it matters are the homepage and the pricing page, because those are the two that carry the gradients. The content pages, like the features index, only show the colour flattening. Signing up is free and takes no card, but nothing in this post needs an account: every measurement above is from the public HTML and the public stylesheet.
Top comments (0)