Seamless Backgrounds on Mobile Safari

One of the worst parts of designing websites is inevitably having to deal with Safari in general, and Safari on iOS in particular.

For some reason the default treatment of websites involves ugly chrome bars above and below your content. I have long been annoyed by this, but working on something today finally sat down to figure out how to fix it well.

Default state

You will see this all the time, if you haven’t noticed it already (my apologies), but here is an example I happened upon in the wild today: Square’s beautiful hero for their new Kiosk page is simply ruined by the iOS status bar and Safari’s control bar. Steve would have never let this happen.

Square’s blue Kiosk hero on iPhone, surrounded by white Safari status and toolbar areas.
Square’s Kiosk page, captured in Safari on iPhone.

The simple fix

You might think to yourself, “Ah but Josh, there is a theme-color meta tag expressly for this purpose!”

<meta name="theme-color" content="#004bff" />

Unfortunately this alone doesn't produce perfect results. Currently in Safari, this works well for installed apps but not for apps in the browser. While it can change dynamically, it doesn't allow you to have separate top and bottom colors.

Safari has a surprisingly useful but not widely known behavior: it can extend the background of a fixed element behind its controls.

So, if you add a thin strip to the top and bottom edge, Safari can use them behind the status bar and app bar. Here, both strips and the document backgrounds use the same blue.

html, body {
  background: #004bff;
}

.edge-top::after, .edge-bottom::after {
  content: "";
  position: fixed;
  left: 0;
  right: 0;
  pointer-events: none;
}
.edge-top::after {
  top: 0;
  height: max(12px, env(safe-area-inset-top));
  background: #004bff;
}
.edge-bottom::after {
  bottom: 0;
  height: max(12px, env(safe-area-inset-bottom));
  background: #004bff;
}
The recreated Square Kiosk page on iPhone, with blue continuing behind Safari’s status bar and bottom controls.View

Dynamic colors

Let’s look at the same Square design, but where we might want to use a gradient that deepens as you scroll. All we have to do is change the colors based on scroll position, and update the page background, edge guides, and document backgrounds together. We can also keep the meta tag in sync with the top color for browsers that support it.

The gradient page at the top, with blue behind Safari’s status bar and rose behind its bottom toolbar.The same page after scrolling, with deep indigo and plum continuing behind Safari’s controls.
View

Here’s an interactive version to make it clear what’s happening:

.edge-top::after {
  background: #004bff;
}
.edge-bottom::after {
  background: #db657a;
}

Limitations

This works as long as each edge is a solid color, even if those colors change as you scroll. It won’t extend images behind Safari’s controls, or perfectly match a gradient that changes color from left to right.

Prompt

Ultimately it’s fairly simple, if not obnoxious, to make your sites look good in mobile Safari, but even still here’s a prompt you can use to implement this technique:

Make the colors behind mobile Safari’s status bar and bottom controls match the visible top and bottom edges of this page, including when its background changes as you scroll. Use one set of color values for the page background, two thin fixed edge strips, and the document backgrounds. Create aria-hidden .edge-top and .edge-bottom elements outside transformed ancestors. Paint their ::after pseudo-elements with --edge-top-background and --edge-bottom-background. Make them full-width, pointer-events: none, and max(12px, env(safe-area-inset-top)) or max(12px, env(safe-area-inset-bottom)) tall. Keep the strips clear of content and match each one to its adjacent background. Set the html background to the top color and the body background to the bottom color. Include a theme-color meta tag for supporting browsers and update it with the top color. Use viewport-fit=cover while keeping text and controls inside the safe areas. If the palette changes, calculate it once and apply the same values to the visible background, both strips, the document backgrounds, and the meta tag. Handle scrolling, restored scroll positions, viewport resizing, and Safari’s expanding or collapsing toolbar. Remove listeners and restore any shared document styles when leaving the page.