pavel 1ar.ionov

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-color on the element itself
  • backdrop-filter on 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: absolute children of fixed elements — ignored entirely
  • Pseudo-elements (::before, ::after) — invisible to the tinting algorithm
  • theme-color meta 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: 0 elements — a fixed element with opacity: 0 still has its background-color and backdrop-filter parsed. Invisible doesn’t mean ignored
  • pointer-events: none elements — 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 also position: 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

  1. Viewport meta: add viewport-fit=cover — required for bottom bar transparency
  2. HTML background: set explicitly — Safari uses it as fallback
  3. Fixed header: transparent background, no backdrop-filter on the fixed element itself. Glass effect on an absolute child
  4. Hidden overlays: use display: none when inactive, not just opacity: 0
  5. Theme color: still fine to set for older browsers, but Safari 26 ignores it
  6. Layout padding: account for safe area insets since viewport-fit=cover extends 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 filterbrightness(0.5) doesn’t carry through to the keyboard accessory bar’s rendering. The blur does, brightness doesn’t. No explanation why.
  • Opacityopacity: 0.4 kills the blur effect entirely when combined with filter: 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

  1. Viewport meta: viewport-fit=cover — required for bottom bar transparency
  2. html background: set explicitly — Safari uses it as fallback
  3. Fixed header: transparent background, no backdrop-filter on the fixed element itself. Glass effect on an absolute child
  4. Hidden overlays: use display: none when inactive, not just opacity: 0
  5. 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
  6. Theme color: still fine to set for older browsers, but Safari 26 ignores it
  7. Layout padding: account for safe area insets since viewport-fit=cover extends content into safe areas

Further reading