Reduced transparency
A stated operating-system preference, answered rung by rung with an opaque fallback that changes paint and never layout.
Overview
Some readers turn transparency off. On macOS and iOS it is Reduce Transparency; on Windows it is the Transparency effects switch. People do it because translucent surfaces are visually noisy, because a busy backdrop through a blurred panel is genuinely harder to parse with low vision or a cognitive impairment, and sometimes because it makes an old device feel faster.
It is a stated preference, not a hint, and opsinjs answers it completely: every translucent rung becomes opaque and drops its blur. The answer is opaque, not "less transparent". A preference answered halfway is a preference ignored with extra steps.
The thing this is most often confused with is reduced motion. They are unrelated preferences with unrelated causes, and a reader may have either, both or neither. Answering both with one switch is a common shortcut and a wrong one. See Reduced motion.
How it works
The query, and what it changes
@media (prefers-reduced-transparency: reduce) {
/* Rungs 3 and 4 take the opaque card colour and lose their blur.
Rung 5 becomes MORE opaque, not less. Nothing else moves. */
}The override sits in the generated token layer, so it applies to both the
documentation chrome and the product theme from one source, and it touches four
of a rung's eight properties: tint, tint-alpha, blur and saturation.
border, shadow, scrim and opaque are deliberately left alone, which is
invariant 2 below. The emitter writes the block for all six rungs rather than
only the three translucent ones, so a component can read any rung's blur and be
right with no conditional.
One component reads the query as well. Surface hides its own backdrop layer
under [@media(prefers-reduced-transparency:reduce)], because the token layer
can collapse the blur to zero but cannot stop the element being a backdrop root.
That is the component's half of the same answer, not a second policy.
Rung by rung
| Rung | Normally | Under reduced transparency |
|---|---|---|
0 canvas | opaque | unchanged |
1 card | opaque | unchanged |
2 raised | opaque, shadowed | unchanged |
3 sheet | tinted alpha, large blur | the opaque card colour, blur removed; border and shadow kept |
4 overlay | tinted alpha, largest blur | the opaque card colour, blur removed; border and shadow kept |
5 scrim | dark wash, minimal blur | higher opacity, blur removed |
The four invariants
1. Layout does not move. The fallback changes background-color and
backdrop-filter and nothing else. No padding, no border width, no size. A
reader toggling the preference mid-session sees the paint change and the page
stay exactly where it was, and a reader who has it on permanently gets an
identically proportioned product rather than a variant nobody tested.
2. The rung stays distinguishable. Borders and shadows are kept, not
removed. Translucency was one of several cues that a sheet is above the page
and that an overlay is chrome; take it away and the others have to carry the
separation on their own, which is why rungs 3 and 4 have a border at all rather
than relying on the blur edge.
3. The scrim gets stronger, not weaker. This is the one that surprises people. A scrim's job is to isolate the overlay from what is behind it, and part of that isolation was being done by the blur. Remove the blur and the wash has to do more work, so its opacity goes up. A scrim that got lighter under this preference would leave a fully legible, fully distracting background behind a modal.
4. Contrast improves; it never regresses. For rungs 3 and 4 the fallback is
an opaque colour that is already a measured pair with the ink on it, which is the
same pair card uses. So the reduced-transparency path is, by construction, at
least as readable as the default path. That is the reason the fallback is the
card colour rather than a new value: a new value would need its own measurements
and would be the least-tested surface in the system. Rung 5 is the exception and
is not a counter-example: a scrim's fallback is its own wash at a higher alpha,
because nothing sits on a scrim to be measured and a scrim forced fully opaque
would be a blank screen.
Support, and why an in-app switch is still worth having
prefers-reduced-transparency is not exposed by every browser engine, and a
reader can have the preference set at the operating-system level while the
browser never tells the page about it. That is a real gap, not a hypothetical
one, and it means the media query alone leaves some readers with the preference
unanswered.
| Feature | Your browser | What happens without it |
|---|---|---|
| backdrop-filter | Checking… | Translucent material rungs fall back to their opaque colour. Layout is unchanged and the contrast floor is unchanged, because the fallback is the value the floor was measured against. |
| corner-shape (squircle) | Checking… | A plain rounded rectangle at the same radius. A taste difference, never a functional one. |
| Display-P3 colour | Checking… | The sRGB ramp. The escalation only raises chroma and keeps hue and lightness identical. Every measured contrast figure therefore holds in both gamuts. |
| linear() easing | Checking… | The browser's default easing. A spring is one custom property holding one linear() value, so there is no cubic-bezier approximation behind it: the declaration that substitutes it is invalid at computed-value time and the timing function is lost. Identical duration and identical end state; the curve is what goes. |
| :has() | Checking… | The status matrix filter and a handful of docs-only affordances stop filtering and show every row. Nothing in the product theme depends on it. |
Tested live with CSS.supports() in the browser you are reading this in. Nothing here is a claim about a browser matrix somebody maintained by hand.
Specify an in-product override that forces the same fallback, so that a reader
whose browser does not forward the operating-system preference can still get
it. It would be a data-reduced-transparency attribute on the root, settable
from the consuming app's own accessibility settings. The token layer would gain
an attribute selector alongside the media query; the open question is whether
the attribute should be able to force transparency back on, which we
currently think it should not.
Until that exists, a product that serves a population likely to use this preference should consider making its own toggle and applying the opaque fallback itself. That is a supported thing to do, because the tokens are plain custom properties and can be overridden on a scope.
Using it
Do not test only the default path. A translucent surface that has never been seen opaque is a surface whose border was tuned to sit next to a blur. Turn the preference on and look at the screen; it is a two-second check and it catches the two failures below.
Do not remove the border along with the blur. The most common bug in this
area is an overlay that becomes an opaque rectangle with no edge, floating in
the middle of the content it was supposed to be chrome for.
Do not use backdrop-filter for anything that is not a material. None of a
blurred decorative flourish, a blurred image treatment and an animated blur
behind a hero is in the ladder, so none of them is covered by the token override,
and all of them stay blurred for a reader who asked for no blur.
Do
Consume rungs through the material tokens, so that the preference is answered once, in the token layer, for every surface in the product simultaneously.
Don’t
Write backdrop-blur-lg bg-white/70 on a component. It will not respond to
the preference, it will not respond to print, and its alpha is in no measured
pair.
How to check
- macOS: System Settings → Accessibility → Display → Reduce transparency.
- iOS and iPadOS: Settings → Accessibility → Display & Text Size → Reduce Transparency.
- Windows: Settings → Personalisation → Colours → Transparency effects, off.
- Chromium DevTools: the Rendering panel can emulate the media feature without changing your system settings, which is the fastest way to check a single component.
Expected result in all four: every translucent surface becomes opaque, keeps its border and shadow, and occupies exactly the same box. A failure looks like a surface that is still translucent, a surface that has lost its edge, or a page that reflows.
Tokens
| Token | What it controls | Used by |
|---|---|---|
| --opsin-material-canvas-tintvar(--opsin-neutral-0) | Rung 0 (canvas): the tint over what is behind it. The application background. Nothing is behind it, so nothing shows through it. | surface |
| --opsin-material-canvas-tint-alpha1 | Rung 0 (canvas): how opaque that tint is. | surface |
| --opsin-material-canvas-blur0px | Rung 0 (canvas): backdrop blur radius. | surface |
| --opsin-material-canvas-saturation1 | Rung 0 (canvas): backdrop saturation multiplier. | surface |
| --opsin-material-canvas-bordernone | Rung 0 (canvas): the boundary. | surface |
| --opsin-material-canvas-shadownone | Rung 0 (canvas): the shadow that separates it from what is behind. | surface |
| --opsin-material-canvas-scrim0 | Rung 0 (canvas): the minimum scrim opacity needed for text on this rung to clear the contrast floor. | surface |
| --opsin-material-canvas-opaquevar(--opsin-neutral-0) | Rung 0 (canvas): the opaque substitute used under prefers-reduced-transparency and where backdrop-filter is unsupported. | surface |
| --opsin-material-inset-tintvar(--opsin-neutral-100) | Rung 0.5 (inset): the tint over what is behind it. The recessed well inside a card: the explanatory note a Callout draws inside the reading it annotates, and the track a RangeBar lays its range along. Its fill sits below the card fill in both themes, so the well reads as carved into the card rather than lifted off it. In light it is neutral-100, one step under the white card, which is where a Callout already sat. In dark it is a value between the page and the card, below the card so the well is a recess and above the page so a Callout used on its own is still visible. | callout, range-bar, surface |
| --opsin-material-inset-tint-alpha1 | Rung 0.5 (inset): how opaque that tint is. | surface |
| --opsin-material-inset-blur0px | Rung 0.5 (inset): backdrop blur radius. | surface |
| --opsin-material-inset-saturation1 | Rung 0.5 (inset): backdrop saturation multiplier. | surface |
| --opsin-material-inset-bordernone | Rung 0.5 (inset): the boundary. | surface |
| --opsin-material-inset-shadownone | Rung 0.5 (inset): the shadow that separates it from what is behind. | surface |
| --opsin-material-inset-scrim0 | Rung 0.5 (inset): the minimum scrim opacity needed for text on this rung to clear the contrast floor. | surface |
| --opsin-material-inset-opaquevar(--opsin-neutral-100) | Rung 0.5 (inset): the opaque substitute used under prefers-reduced-transparency and where backdrop-filter is unsupported. | surface |
| --opsin-material-card-tintvar(--opsin-neutral-0) | Rung 1 (card): the tint over what is behind it. The default home for a health value. Opaque, bounded by a line rather than a shadow, and the rung every ResultCard, MetricTile and RangeBar sits on. | surface |
| --opsin-material-card-tint-alpha1 | Rung 1 (card): how opaque that tint is. | surface |
| --opsin-material-card-blur0px | Rung 1 (card): backdrop blur radius. | surface |
| --opsin-material-card-saturation1 | Rung 1 (card): backdrop saturation multiplier. | surface |
| --opsin-material-card-bordervar(--opsin-neutral-200) | Rung 1 (card): the boundary. | surface |
| --opsin-material-card-shadownone | Rung 1 (card): the shadow that separates it from what is behind. | surface |
| --opsin-material-card-scrim0 | Rung 1 (card): the minimum scrim opacity needed for text on this rung to clear the contrast floor. | surface |
| --opsin-material-card-opaquevar(--opsin-neutral-0) | Rung 1 (card): the opaque substitute used under prefers-reduced-transparency and where backdrop-filter is unsupported. | surface |
| --opsin-material-raised-tintvar(--opsin-neutral-0) | Rung 2 (raised): the tint over what is behind it. Menus, popovers, tooltips, a dragged card. Still opaque: the shadow, not translucency, is what says 'above'. | surface |
| --opsin-material-raised-tint-alpha1 | Rung 2 (raised): how opaque that tint is. | surface |
| --opsin-material-raised-blur0px | Rung 2 (raised): backdrop blur radius. | surface |
| --opsin-material-raised-saturation1 | Rung 2 (raised): backdrop saturation multiplier. | surface |
| --opsin-material-raised-bordervar(--opsin-neutral-200) | Rung 2 (raised): the boundary. | surface |
| --opsin-material-raised-shadow0 1px 2px oklch(0 0 0 / 0.06), 0 4px 12px oklch(0 0 0 / 0.06) | Rung 2 (raised): the shadow that separates it from what is behind. | surface |
| --opsin-material-raised-scrim0 | Rung 2 (raised): the minimum scrim opacity needed for text on this rung to clear the contrast floor. | surface |
| --opsin-material-raised-opaquevar(--opsin-neutral-0) | Rung 2 (raised): the opaque substitute used under prefers-reduced-transparency and where backdrop-filter is unsupported. | surface |
| --opsin-material-sheet-tintvar(--opsin-neutral-0) | Rung 3 (sheet): the tint over what is behind it. A bottom sheet or side panel the reader can dismiss by dragging. Its tint is now near opaque, so the panel lands close to the card colour and a white control inside it no longer floats above a greyer surface. What keeps the covered page recognisable is the scrim around the panel rather than the page reading through the panel, because at this opacity very little of the page shows through the tint. | surface |
| --opsin-material-sheet-tint-alpha0.93 | Rung 3 (sheet): how opaque that tint is. | surface |
| --opsin-material-sheet-blur20px | Rung 3 (sheet): backdrop blur radius. | surface |
| --opsin-material-sheet-saturation1.6 | Rung 3 (sheet): backdrop saturation multiplier. | surface |
| --opsin-material-sheet-borderoklch(0 0 0 / 0.08) | Rung 3 (sheet): the boundary. | surface |
| --opsin-material-sheet-shadow0 -1px 2px oklch(0 0 0 / 0.05) | Rung 3 (sheet): the shadow that separates it from what is behind. | sheet, surface |
| --opsin-material-sheet-scrim0.93 | Rung 3 (sheet): the minimum scrim opacity needed for text on this rung to clear the contrast floor. | surface |
| --opsin-material-sheet-opaquevar(--opsin-neutral-0) | Rung 3 (sheet): the opaque substitute used under prefers-reduced-transparency and where backdrop-filter is unsupported. | surface |
| --opsin-material-overlay-tintvar(--opsin-neutral-0) | Rung 4 (overlay): the tint over what is behind it. A pinned toolbar, a tab bar and a floating action bar are all chrome that content scrolls beneath. | surface |
| --opsin-material-overlay-tint-alpha0.74 | Rung 4 (overlay): how opaque that tint is. | surface |
| --opsin-material-overlay-blur28px | Rung 4 (overlay): backdrop blur radius. | surface |
| --opsin-material-overlay-saturation1.8 | Rung 4 (overlay): backdrop saturation multiplier. | surface |
| --opsin-material-overlay-borderoklch(0 0 0 / 0.10) | Rung 4 (overlay): the boundary. | surface |
| --opsin-material-overlay-shadow0 8px 32px oklch(0 0 0 / 0.12) | Rung 4 (overlay): the shadow that separates it from what is behind. | combobox, menu, popover, select, surface, toast, tooltip |
| --opsin-material-overlay-scrim0.74 | Rung 4 (overlay): the minimum scrim opacity needed for text on this rung to clear the contrast floor. | surface |
| --opsin-material-overlay-opaquevar(--opsin-neutral-0) | Rung 4 (overlay): the opaque substitute used under prefers-reduced-transparency and where backdrop-filter is unsupported. | surface |
| --opsin-material-scrim-tintvar(--opsin-neutral-950) | Rung 5 (scrim): the tint over what is behind it. The dimming layer behind a modal dialog or a consent sheet. Its job is to remove the page from consideration, not to look like glass. | surface |
| --opsin-material-scrim-tint-alpha0.44 | Rung 5 (scrim): how opaque that tint is. | surface |
| --opsin-material-scrim-blur2px | Rung 5 (scrim): backdrop blur radius. | surface |
| --opsin-material-scrim-saturation1 | Rung 5 (scrim): backdrop saturation multiplier. | surface |
| --opsin-material-scrim-bordernone | Rung 5 (scrim): the boundary. | surface |
| --opsin-material-scrim-shadownone | Rung 5 (scrim): the shadow that separates it from what is behind. | surface |
| --opsin-material-scrim-scrim0.44 | Rung 5 (scrim): the minimum scrim opacity needed for text on this rung to clear the contrast floor. | surface |
| --opsin-material-scrim-opaqueoklch(0.205 0.006 250 / 0.72) | Rung 5 (scrim): the opaque substitute used under prefers-reduced-transparency and where backdrop-filter is unsupported. | surface |
Accessibility impact
What is guaranteed. Every rung in the ladder has an opaque fallback; the fallback is an already-measured pair; nothing about the layout changes; and the scrim strengthens so that modality remains visually as well as programmatically clear.
What is yours. Any translucency you introduce outside the ladder. Any blur applied to an image rather than to a backdrop. Any component that reads the media query itself rather than consuming the tokens. If you do that, you have taken on the responsibility of keeping your answer consistent with the system's.
What this preference does not tell you. It is not a proxy for low vision, for reduced motion, or for a low-powered device, and it should not be used to infer any of them. A reader who wants opaque surfaces and full motion is entirely ordinary. The three preference queries are independent inputs and the system composes them rather than ranking them; the general contract is on Reduced transparency in the accessibility pillar.
| Pair | Theme | APCA Lc | WCAG 2.2 | Floor |
|---|---|---|---|---|
| body text on the canvas material | light | 104.7 | 17.96:1 | Pass |
| body text on the canvas material | dark | -100.5 | 18.00:1 | Pass |
| body text on the inset material | light | 98.1 | 16.32:1 | Pass |
| body text on the inset material | dark | -100.1 | 17.18:1 | Pass |
| body text on the card material | light | 104.7 | 17.96:1 | Pass |
| body text on the card material | dark | -99.6 | 16.32:1 | Pass |
| body text on the raised material | light | 104.7 | 17.96:1 | Pass |
| body text on the raised material | dark | -97.5 | 13.77:1 | Pass |
| body text on the sheet material over the darkest backdrop | light | 99.9 | 16.76:1 | Pass |
| body text on the sheet material over the darkest backdrop | dark | -99.7 | 16.47:1 | Pass |
| body text on the sheet material over the lightest backdrop | light | 104.7 | 17.96:1 | Pass |
| body text on the sheet material over the lightest backdrop | dark | -80.8 | 6.07:1 | Pass |
| body text on the overlay material over the darkest backdrop | light | 85.9 | 13.48:1 | Pass |
| body text on the overlay material over the darkest backdrop | dark | -99.9 | 16.77:1 | Pass |
| body text on the overlay material over the lightest backdrop | light | 104.7 | 17.96:1 | Pass |
| body text on the overlay material over the lightest backdrop | dark | -54.2 | 2.69:1 | Below floor |
Related
- The ladder has the six rungs and the four properties each fallback replaces.
- The contrast floor explains why the opaque path is always at least as readable as the translucent one.
- Increased contrast is the third preference query, which changes different things again.
The contrast floor
Every translucent surface gets a minimum backing tint, so text contrast never depends on what is behind it, plus the worst-case composite and the scrim rule.
Performance budget
At most three composited surfaces on screen at once, why a blurred backdrop costs what it costs, and how to measure it on the phone your readers actually own.