---
title: "Reduced transparency"
description: "A stated operating-system preference, answered rung by rung with an opaque fallback that changes paint and never layout."
url: "https://opsinjs.pensievelabs.org/foundations/materials/reduced-transparency"
source: "https://opsinjs.pensievelabs.org/foundations/materials/reduced-transparency.md"
section: "Foundations"
kind: "foundation"
reviewed: "2026-09-02"
reviewer: "design"
aliases: ["prefers-reduced-transparency", "opaque fallback", "translucency preference", "no blur", "translucency off"]
---

> Elements written as `<PascalCase … />` below are opsinjs documentation
> components. Their attributes are the content: the values they render are
> generated from `tokens/*.json` and `registry/catalogue.ts` and are
> published separately at https://opsinjs.pensievelabs.org/r/index.json and under the Reference
> section.
> Nothing is missing from this page. The data simply does not live in
> the prose.

<PageTemplate kind="foundation" />

## Overview [#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](../motion/reduced-motion.mdx).

## How it works [#how-it-works]

### The query, and what it changes [#the-query-and-what-it-changes]

```css
@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-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 [#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 [#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.

<BrowserSupport feature="prefers-reduced-transparency" />

<Todo>
  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.
</Todo>

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 [#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.

<DoDont>
  <DoDont.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.
  </DoDont.Do>

  <DoDont.Dont>
    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.
  </DoDont.Dont>
</DoDont>

### How to check [#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 [#tokens]

<TokenTable scope="material" />

## Accessibility impact [#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](../../accessibility/reduced-transparency.mdx).

<ContrastReport scope="materials" />

## Related [#related]

* [The ladder](./the-ladder.mdx) has the six rungs and the four properties each
  fallback replaces.
* [The contrast floor](./the-contrast-floor.mdx) explains why the opaque path is
  always at least as readable as the translucent one.
* [Increased contrast](../../accessibility/increased-contrast.mdx) is the third
  preference query, which changes different things again.
