Safari 26 Liquid Glass: fixing toolbar tinting for web developers
How Safari 26 derives toolbar colors from your CSS, and how to prevent unwanted status bar and bottom bar tinting.
Safari 26 no longer reads
theme-color. It reads your CSS — and it’s pickier than you’d expect.
With Liquid Glass, Apple redesigned every toolbar and status bar to be translucent. Safari’s browser chrome now derives its tint from the web page itself. If your fixed header has a background color, your status bar will match it. If a hidden modal backdrop sits at the bottom of the viewport, your bottom toolbar will turn dark.
No documentation from Apple explains the exact rules. Here’s what I’ve found through trial and error while building 1ar.io.
The tinting algorithm
Safari 26 scans for position: fixed or position: sticky elements near the viewport edges and reads two properties:
background-coloron the element itselfbackdrop-filteron the element itself
It uses these to compute the tint color for the nearest toolbar (status bar at the top, Safari toolbar at the bottom).
What Safari does NOT sample
position: absolutechildren of fixed elements — ignored entirely- Pseudo-elements (
::before,::after) — invisible to the tinting algorithm theme-colormeta tag — Safari 26 ignores it completely. It was the old mechanism; now tinting is purely CSS-derived- Non-fixed elements — regular flow content, even if visually near the edge, doesn’t affect tinting
What Safari DOES sample (even when you don’t expect it)
opacity: 0elements — a fixed element withopacity: 0still has itsbackground-colorandbackdrop-filterparsed. Invisible doesn’t mean ignoredpointer-events: noneelements — same as above. The element exists in the render tree, Safari reads its styles- Elements inside fixed parents that themselves have
position: fixed— if a child inside your fixed header is alsoposition: fixed, Safari evaluates it independently
This means hidden overlay backdrops, modal containers, and dropdown panels — the kind that sit in the DOM with zero opacity and disabled pointer events until toggled — will affect toolbar tinting unless you use display: none.
Fixing the top bar (status bar)
The status bar tints based on fixed elements within ~4px of the viewport top. A typical glassmorphism header breaks this immediately:
<!-- broken: Safari tints status bar with header's bg + blur -->
<header style="position: fixed; top: 0;
background-color: rgba(255,255,255,0.8);
backdrop-filter: blur(12px);">
</header>
The fix: transparent parent, absolute child
Move all visual properties to a position: absolute child. Safari sees the fixed parent as transparent and ignores the absolute child.
<header style="position: fixed; top: 0;
background-color: transparent;">
<!-- Safari ignores this because it's position:absolute -->
<div style="position: absolute; inset: 0;
background-color: rgba(255,255,255,0.8);
backdrop-filter: blur(12px);"
aria-hidden="true">
</div>
<!-- nav content, z-index above the glass -->
<nav style="position: relative; z-index: 1;">...</nav>
</header>
The header stays transparent — no background-color, no backdrop-filter on the fixed element itself. The glass effect lives on a child div that Safari’s tinting algorithm skips.
Fixing the bottom bar (Safari toolbar)
The bottom bar follows the same logic but with an extra requirement: viewport-fit=cover.
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
Without viewport-fit=cover, the page content stops at the safe area boundary. Safari renders a solid bar in the gap between your content and the toolbar. With viewport-fit=cover, content extends behind the toolbar, giving Liquid Glass something to show through.
You also want the html element to declare its background explicitly:
html {
background-color: var(--your-page-bg);
}
Safari falls back to the html or body background when no qualifying fixed element is found near the bottom. If you don’t set it, the fallback is white (light) or black (dark) — which looks like a solid bar.
Hidden fixed elements break the bottom bar
This is the subtle one. If you have a modal backdrop like this:
.modal-backdrop {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(2px);
opacity: 0;
pointer-events: none;
}
It spans the entire viewport including the bottom edge. Safari reads its background-color and backdrop-filter — even at opacity: 0 — and tints the bottom toolbar dark.
The fix: display none when hidden
.modal-backdrop {
display: none; /* completely removed from render tree */
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(2px);
}
.modal-backdrop.is-open {
display: block;
opacity: 1;
}
When the element uses display: none, it doesn’t exist in the render tree. Safari can’t sample it. Toggle to display: block when you need it, and Safari will tint the toolbar accordingly (which is correct — when a modal is open, you want the toolbar to reflect the overlay).
If you need an opacity transition, set display: block first via JavaScript, then add the opacity class in the next animation frame:
function openModal() {
backdrop.style.display = 'block';
requestAnimationFrame(() => {
backdrop.classList.add('is-open');
});
}
function closeModal() {
backdrop.classList.remove('is-open');
setTimeout(() => {
backdrop.style.display = 'none';
}, 200); // match CSS transition duration
}
Quick checklist
- Viewport meta: add
viewport-fit=cover— required for bottom bar transparency - HTML background: set explicitly — Safari uses it as fallback
- Fixed header: transparent background, no backdrop-filter on the fixed element itself. Glass effect on an absolute child
- Hidden overlays: use
display: nonewhen inactive, not justopacity: 0 - Theme color: still fine to set for older browsers, but Safari 26 ignores it
- Layout padding: account for safe area insets since
viewport-fit=coverextends content into safe areas
Known issues
Safari’s tinting behavior with fixed elements has been filed as WebKit Bug #302272 (duplicate of #300965). Safari 26.2 fixed fullscreen dialog backdrops not extending below the address bar, but the general fixed-element tinting behavior appears to be by design rather than a bug — it’s how Liquid Glass is supposed to work. Web developers just need to account for it.
Ben Frain has a detailed write-up on the same issue, focused on dialog and popover elements.
The iOS keyboard gap: another layer of nonsense
Here’s one Apple didn’t bother telling anyone about.
When the software keyboard is open on iOS 26, there’s a native keyboard accessory bar — the strip with ^ v ✓ buttons that sits between your web content and the keyboard. This bar renders a slice of your web page outside your overlay’s compositing context. Your backdrop filter? Doesn’t reach it. Your fixed overlay? Doesn’t cover it. The keyboard accessory bar does its own thing.
The result: if you have a command palette or search overlay open, the area between your input field and the keyboard shows raw page content. Sharp, unblurred, fully readable text peeking through — while the rest of your carefully crafted overlay dims everything else.
No CSS property controls this. No backdrop-filter reaches it. No opacity or z-index tricks help. It’s native UI rendering a snapshot of your web view from a compositing layer you cannot access.
The fix: blur the actual content
Instead of trying to blur through the overlay (backdrop-filter on a fixed element), blur the source content itself:
function openOverlay() {
const main = document.querySelector('.layout-main');
const footer = document.querySelector('footer');
if (main) main.style.filter = 'blur(8px)';
if (footer) footer.style.filter = 'blur(8px)';
}
function closeOverlay() {
const main = document.querySelector('.layout-main');
const footer = document.querySelector('footer');
if (main) main.style.filter = '';
if (footer) footer.style.filter = '';
}
This works because filter: blur() modifies the rendered DOM content at the source. When the keyboard accessory bar grabs its slice of the web view, it gets the blurred version. The gap between your input field and the keyboard now shows blurred content instead of readable text.
What doesn’t work (I tried all of these)
- Body background color — the gap shows rendered DOM elements, not the body background. Setting it to dark changes nothing visible in the gap.
- Transparent html background — hoping Safari would fall back to Liquid Glass for the top bar. Instead it falls back to white. Thanks.
- Brightness filter —
brightness(0.5)doesn’t carry through to the keyboard accessory bar’s rendering. The blur does, brightness doesn’t. No explanation why. - Opacity —
opacity: 0.4kills the blur effect entirely when combined withfilter: blur(). Two CSS properties that should compose, but don’t. - Dark background on content elements — affects the visible content through the overlay (making it too dark), but doesn’t affect the keyboard gap area. The gap shows its own rendering.
What partially works
You can darken the footer’s background to make the bottom gap darker, but you can’t match the overlay’s exact dimming level — the footer is visible both through the overlay (where it compounds) and in the gap (where it doesn’t). You’d need different opacity in two places, and you only have one element.
On Apple’s documentation
There is no Apple documentation for any of this. Not the tinting algorithm. Not the sampling rules. Not the keyboard accessory bar compositing behavior. Not even a mention that theme-color is now ignored.
The WebKit blog posts announce features. The WWDC sessions demo Liquid Glass on native apps. The web gets a blog post about new CSS properties and a pat on the head.
Everything in this article was found through trial and error while building 1ar.io. The tinting sampling rules (4px from top, 3px from bottom, 80% viewport width) come from community reverse-engineering. The keyboard accessory bar behavior came from hours of testing on a real device because the simulator doesn’t reproduce half of these issues.
Apple shipped a design system that fundamentally changes how web pages render in their browser, published zero documentation for web developers, and left the community to figure it out through console.log and screenshots. The one relevant WebKit bug got closed as “by design.”
Liquid Glass is beautiful on native apps where Apple controls every layer. On the web, it’s a minefield of undocumented compositing behaviors, native UI layers that ignore your CSS, and a tinting algorithm that reads properties from elements you thought were hidden. Every fix is a workaround. Every workaround breaks something else. The only thing Safari is consistent about is being inconsistent.
Updated checklist
- Viewport meta:
viewport-fit=cover— required for bottom bar transparency htmlbackground: set explicitly — Safari uses it as fallback- Fixed header: transparent background, no backdrop-filter on the fixed element itself. Glass effect on an absolute child
- Hidden overlays: use
display: nonewhen inactive, not justopacity: 0 - Content blur for overlays: apply
filter: blur()to main content and footer when an overlay is open — the only way to blur the keyboard accessory bar gap on iOS - Theme color: still fine to set for older browsers, but Safari 26 ignores it
- Layout padding: account for safe area insets since
viewport-fit=coverextends content into safe areas
Further reading
- How Liquid Glass works — displacement maps, 2D refraction, and gyroscope tricks
- andesco/safari-color-tinting — community-built test suite for Safari tinting behavior
- Ben Frain on iOS 26 tinting —
dialogandpopoverelement tinting issues - Edoardo Lunardi on fixed overlays — the
opacity: 0.99compositing hack - Mike Piontek on Safari 26 — “Safari 26 and Liquid Glass have little respect for web sites”
- What’s new in Safari and WebKit — WWDC25 — official session (doesn’t cover tinting)
- WebKit features in Safari 26.0 — release notes