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.

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;
}
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.


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.



