A customer of ours emailed: "Your Chrome extension shows me a blank white page on our dashboard." I checked. His site uses color-scheme: dark\. Ours doesn't set it. Our iframe was painting a solid white rectangle over his entire viewport — and I had no idea, because every site I test on is light-themed.
The white rectangle nobody sees
Drop an iframe into a customer's page. Set background: transparent\ on its <body>\, its <html>\, even #root\. Check DevTools — every layer reports background-color: rgba(0, 0, 0, 0)\. The iframe should be see-through.
On most sites, it is.
On any site that sets color-scheme: dark\ on its <html>\ element, the iframe paints a solid white rectangle over everything underneath. Your transparent body is sitting on top of a white backdrop you didn't ask for and can't see in the computed styles panel.
---
Why Chrome does this
The CSS property responsible is color-scheme\. It tells the browser what theme a document expects — light\, dark\, or normal\. The browser uses this to pick the right shade for form controls, scrollbars, and a system color called Canvas.
Canvas\ is the default backdrop the browser paints behind any element with a transparent background. In light mode it's white. In dark mode it's near-black.
The spec detail that matters: color-scheme\ does not propagate across iframe document boundaries. Every iframe document defaults to color-scheme: normal\ (which resolves to light) regardless of what the parent page declares.
The CSS Color Adjustment spec calls this a color-scheme mismatch. The spec requires the user agent to substitute an opaque Canvas color matching the iframe document's resolved color scheme — light, in this case, which means white. Your transparent body is then composited on top of that opaque white backdrop, not on top of the dark parent page.
In practice, this means the browser inserts an opaque layer between your iframe document and the parent page before any of your own CSS gets a chance to run. There is no DOM node you can inspect for it. The computed background-color on every element in your iframe still reports rgba(0, 0, 0, 0)\. The Elements panel will not show you a white rectangle, because the white rectangle is not in the DOM — it is the user agent's substitute Canvas, painted by the compositor.
This is documented in Chromium issue 40157837 and the CSSWG drafts discussion. It is intentional, it is spec-compliant, and it has been breaking iframe overlays for years — one missing HTML tag has been killing social media traffic the same way for just as long. The web is full of these tiny spec gotchas.
---
The two-line CSS fix
Tell the iframe element explicitly what color scheme to use, and make sure its body is transparent:
iframe.my-overlay {
color-scheme: light;
background: transparent;
}
That's it. color-scheme: light\ on the iframe element in the parent document tells Chrome "the iframe wants light scheme, same as you would assume by default." No mismatch, no Canvas backdrop. background: transparent\ makes sure the element itself doesn't paint either.
If you control the iframe document too, also add this in its CSS so it's transparent even before the inline style is parsed:
html, body, #root {
background: transparent !important;
color-scheme: light !important;
}
!important\ matters because some CSS resets and base layers (including configurations that add a body background) override your plain rule.
Small fixes like this are the kind of thing I collect in The Claude Code Memory Starter — a free email series you can join in one click.
---
The reusable React component
If you have one iframe, do the CSS-only fix. If you have ten — which is what I had across our flow editor — you need a wrapper so nobody can add an iframe and forget the rule.
import { forwardRef, IframeHTMLAttributes } from 'react';
export const SafeIframe = forwardRef<HTMLIFrameElement, IframeHTMLAttributes<HTMLIFrameElement>>(
function SafeIframe({ style, ...rest }, ref) {
return (
<iframe
ref={ref}
{...rest}
style={{
background: 'transparent',
colorScheme: 'light',
...style,
}}
/>
);
}
);
Two things to notice. The wrapper uses forwardRef\ so callers can still attach refs (you'll need that for getBoundingClientRect()\ and postMessage\ patterns common in overlay UIs). And the default style spread comes before ...style\, so any caller-provided style overrides the defaults — including setting colorScheme: 'dark'\ for the rare iframe that actually wants the dark backdrop.
Then a sed\ pass replaces native iframes with the wrapper:
for f in src/components/**/*.tsx; do
sed -i '' 's|<iframe|<SafeIframe|g; s|</iframe>|</SafeIframe>|g' "$f"
done
Add the import in each touched file. Done. Every future iframe added to the project is transparent-by-default unless somebody actively opts out.
---
Make the iframe document transparent too
The wrapper fixes the iframe element. The CSS above fixes the iframe document. You want both, because each protects against a different failure mode.
The element-level fix breaks if a caller passes an explicit style={{ colorScheme: 'dark' }}\ override. The document-level fix breaks if the iframe is created somewhere outside your wrapper (a third-party library, a manual document.createElement('iframe')\).
Add the document-level CSS to your iframe app's root stylesheet:
html, body, #root {
background: transparent !important;
color-scheme: light !important;
}
If your iframe app is React-based and uses Tailwind, put this after @tailwind base\ so it overrides preflight defaults.
---
Why this bug is invisible on light-themed sites
If you don't test on a dark-themed host, you will never see this bug. The white Canvas blends into a light parent page — you see what looks like a transparent overlay, but it's actually a white rectangle on top of a white page.
The bug is identical on light and dark hosts. Only the visibility changes. My dev environment is light. The marketing site is light. Most customer playground apps are light. The bug existed for months, and nobody on the team saw it because the entire test surface looked clean. The customer who reported it ran a security product with a dark dashboard — as soon as the white rectangle had something to contrast against, the "blank page" complaints started. (Same shape as our Next.js app that crashed every 24 hours — silent production bug only outsiders ever noticed.)
---
How to test for it before customers do
Add this to your release checklist:
- Open any of your iframe overlays on a host page that sets
color-scheme: dark\on its<html>\. - If you don't have such a customer, force it for testing: in DevTools, find the
<html>\of any site you're testing on, addstyle="color-scheme: dark"\inline. The bug will appear immediately if it's there. - Or open Chrome DevTools → Rendering tab → set "Emulate CSS media feature prefers-color-scheme" to dark, then visit any site whose CSS respects the system preference.
If your overlay paints a visible rectangle, you have the bug. The fix is the two-line CSS rule above.
In DevTools the rectangle is solid white, the exact Canvas\ light-mode color — not off-white, not semi-transparent. It appears the instant the iframe element gets a layout box, before any of your iframe document's own CSS runs, so there is no flash-of-transparent-content moment to catch. To verify the fix is working, apply it, hard-reload the host page, and confirm the iframe area composites cleanly against the dark parent background with no white edge anywhere along its bounding box.
---
What we shipped
In our codebase, the fix touched four files, replaced sixteen native <iframe>\ elements with one SafeIframe\ wrapper component, added one CSS rule to the global stylesheet, and deleted around twenty inline colorScheme: 'light'\ hot-patches that I had been sprinkling around while hunting the bug.
The wrapper component is fifteen lines. The CSS rule is three lines. The entire fix is shorter than this paragraph.
The bug had no console error, no DevTools warning, no broken selector. I only found it because a customer's screen didn't match mine.
If you inject UI into customer pages, set up a dark-themed test host. I found mine had been broken on dark customers for months.
---
If this saved you a dark-mode debugging session, The Claude Code Memory Starter is a free email series where I share more fixes like it — one short email at a time.




Top comments (0)