Transparent iFrames in Dark Mode
When building Lafyeri, our Edge-native comment engine, we relied heavily on <iframe> embeds to inject comments into host blogs without causing CSS conflicts. To make the widget look completely native to the host site, we used a simple strategy: set the iframe’s background to transparent and let the host blog’s background color show through.
In Light Mode, it worked flawlessly.
But when the host site switched to Dark Mode, our beautifully transparent widget suddenly got slapped with a harsh, solid dark gray background. No matter how aggressively we applied background: transparent !important to the widget’s <body> and <html>, the browser outright ignored us.
After some head-scratching, we discovered this isn’t a bug—it’s a browser feature.
The Mystery of the Opaque Canvas
To support native dark mode scrollbars and form inputs inside the iframe, our widget dynamically applies the color-scheme: dark property to its root element when it detects a dark host.
However, an excellent deep-dive by Florens Verschelde in the post Transparent iframes and dark mode shed light on exactly why this breaks transparency.
According to the CSS Working Group specifications:
“If the used color scheme of an iframe differs from the embedding document, the iframe gets an opaque canvas background appropriate to its color scheme.”
In plain English: If the host page’s <iframe> tag does not explicitly declare the same color-scheme as the document loaded inside the iframe, the browser panics. It assumes that a transparent background might lead to unreadable text (e.g., white text on a white host background) and forcefully paints an opaque dark canvas behind your iframe document.
The Problematic Setup
Here is what was happening under the hood. Our embed script was simply creating an iframe:
// embed.js (Host Site)
const iframe = document.createElement('iframe');
iframe.src = 'https://lafyeri.zerdalu.com/widget';
iframe.style.background = 'transparent'; // Ignored by the browser!
document.body.appendChild(iframe);
And inside our widget, we were doing this:
/* widget.css (Inside the iframe) */
:root.dark {
color-scheme: only dark;
background-color: transparent;
}
Because the parent <iframe> element on the host page defaulted to color-scheme: light (or normal), but the inner document declared color-scheme: dark, the browser stepped in and forced an opaque background.
The Fix: Syncing the Color Scheme
To solve this, we don’t just need to change the theme inside the widget. We need to explicitly tell the browser that the <iframe> element itself supports the dark color scheme.
By applying color-scheme directly to the iframe tag on the host page, the browser sees a match between the parent element and the embedded document, and it allows the transparent background to work.
Here is how we updated our embed script to fix it:
// embed.js (Host Site)
const iframe = document.createElement('iframe');
iframe.src = 'https://lafyeri.zerdalu.com/widget';
// 1. Detect the host's current theme ('light' or 'dark')
const currentTheme = getHostTheme();
// 2. Apply the color-scheme directly to the iframe tag!
iframe.style.cssText = `
width: 100%;
border: none;
background: transparent;
color-scheme: ${currentTheme}; /* This line is the magic fix */
`;
// Ensure standard transparency attributes as a fallback
iframe.setAttribute('allowtransparency', 'true');
document.body.appendChild(iframe);
Furthermore, if your host site allows users to toggle themes dynamically, you must ensure that whenever the theme changes, you update the color-scheme on the iframe element simultaneously:
// Listen for theme changes on the host
const syncState = () => {
const newTheme = getHostTheme();
// Keep the iframe element's color-scheme perfectly synced
iframe.style.colorScheme = newTheme;
// Tell the inner widget to update its own theme
iframe.contentWindow.postMessage({ theme: newTheme }, '*');
};
// Observe the host's <html> or <body> for class changes
const observer = new MutationObserver(syncState);
observer.observe(document.documentElement, { attributes: true });
Conclusion
If you are building embeddable widgets, pay close attention to color-scheme. What seems like a simple CSS background issue is actually a strict browser-level accessibility feature.
By keeping the color-scheme of the <iframe> element perfectly synced with the document embedded inside it, you keep the browser happy—and your backgrounds transparent.