A user sent me a screen recording of my own changelog page looking subtly, infuriatingly broken — a sliver of content peeking out above the navbar, like the page had loaded scrolled slightly past the top. Only on their iPhone. Never on my desktop, my Android phone, or any emulator I threw at it.
This is the story of three attempts to fix it, two of which made things worse, and the one line of CSS actually responsible.
Attempt 1: Safe area
iPhones with a notch or Dynamic Island reserve space at the top of the screen, and CSS has an env variable for exactly this: env(safe-area-inset-top). A sliver of misplaced content near the top of the screen, on iPhone specifically — this felt like a textbook safe-area problem. I added viewport-fit=cover to the viewport meta tag and padded the nav with the safe-area inset.
The nav didn't just get padding — it lost most of its usable height. Suddenly I could only see about half the navbar unless I scrolled and held at the very top, and it would bounce back a moment later. I'd fixed nothing and broken something else.
viewport-fit=cover changes how the whole page's height is calculated relative to the viewport, and my layout leaned on h-screen in a few places. Changing the viewport contract out from under a fixed-height layout is asking for exactly this kind of collapse. Reverted both changes.
Attempt 2: Hide the seam
Next theory: maybe the nav background just wasn't opaque enough to cover whatever was bleeding through underneath it, and the "gap" was really page content showing through a translucent nav.
// Before
className="bg-slate-950/70 supports-[backdrop-filter]:bg-slate-950/55"
// Attempt two
className="bg-slate-950 supports-[backdrop-filter]:bg-slate-950/80"This made the nav look a little cleaner, but the actual bug — the page loading scrolled slightly past the top — was still there. I'd papered over a symptom without finding the cause. Worth keeping the opacity change for its own sake, but it wasn't the fix.
Two wrong attempts in, the only reliable move left was to stop theorizing about what iOS does and go find out what iOS actually does with this specific page.
Root cause
The page had one detail every other page on the site didn't: a wrapper div using overflow-x-clip around an animated, horizontally-sliding detail panel. The intent was narrow — clip horizontal overflow during the slide-in animation, leave vertical scrolling alone.
iOS Safari doesn't see it that way. On iOS Safari specifically, overflow-x: clip can corrupt the scroll-container geometry for the whole element, affecting both axes rather than just the one you named. The practical result was a page that loaded already scrolled slightly past its actual top — precisely the sliver-of-content-above-the-nav symptom from the very first screen recording.
// The wrapper that caused it
<div className="overflow-x-clip">
<TimelinePanel />
</div>
// The fix: remove it. The animated panel inside already has
// overflow-hidden of its own, which is enough to contain the slide.
<TimelinePanel />While I was in there, I also found a second, unrelated iOS quirk on the same page: a flex-col-reverse layout (used so the timeline rendered in a particular visual order) interacting badly with iOS Safari's scroll-anchoring, which was independently causing the page to sometimes load scrolled to the bottom instead of the top. Switched it to a plain flex-col with an md:order-first instead, which achieves the same visual order without touching scroll direction at all.
Lesson
Two real bugs, both invisible outside iOS Safari, both hiding behind CSS properties that do exactly what you'd expect everywhere except the one browser engine that matters most for mobile traffic. My first two attempts treated the symptom (visual gap, translucent background) instead of the mechanism (corrupted scroll geometry) — which is the natural trap when a bug only reproduces on hardware you're not holding.
The actual lesson wasn't about CSS specifics — it's that "this only happens on iOS" is a hypothesis about the platform, not an excuse to guess at UI-level fixes. Once I treated it as a real platform quirk worth researching instead of a look I could paper over, it took one search and one wrapper removal to fix two separate bugs at once.