Fixed bottom navigation in a WordPress PWA, without the iOS bugs
A bottom nav bar that stays put sounds trivial until you build one. On iOS it drifts, hides behind the home indicator, or jumps every time the keyboard opens. Here is why, and what actually fixes it.
Why position: fixed is not enough
The obvious approach is a bar pinned with position:fixed; bottom:0. It works in desktop browsers and it works in Chrome on Android. On an iPhone it fails in three separate ways, and each one has a different cause.
1. The home indicator sits on top of your buttons
On any iPhone without a physical home button, the bottom of the screen is occupied by the home indicator. A bar at bottom:0 renders underneath it, so the bottom few millimetres of your navigation are permanently covered and the tap targets are partly unreachable.
The fix is the safe area inset, but it only exists if you opt in. Without viewport-fit=cover in the viewport meta tag, the safe area variables are always zero and the padding silently does nothing:
<meta name="viewport"
content="width=device-width, initial-scale=1, viewport-fit=cover">
.bottom-nav {
position: fixed;
bottom: 0;
padding-bottom: env(safe-area-inset-bottom, 0px);
}
That combination is the part most people miss. The CSS gets copied from a blog post, the meta tag does not, and the padding evaluates to zero on every device.
2. The bar moves when Safari’s toolbar hides
Safari on iOS shrinks its own chrome as you scroll, which changes the viewport height mid-scroll. Anything sized with 100vh or anchored against it appears to drift. In a PWA launched from the Home Screen this mostly goes away, because there is no browser chrome to collapse, which is why a bar that looks broken in Safari can look fine once installed.
Testing in a Safari tab and assuming the installed app behaves the same is a reliable way to chase a bug that is not there, and to miss one that is.
3. The keyboard pushes it up the screen
When an input takes focus, iOS resizes the visual viewport and a fixed bar rides up to sit on top of the keyboard. Sometimes that is what you want. In a navigation bar it is not, because it covers the field the user is typing into.
The visual viewport API is what tells you this is happening:
if (window.visualViewport) {
window.visualViewport.addEventListener('resize', function () {
var shrunk = window.visualViewport.height < window.innerHeight - 120;
document.body.classList.toggle('keyboard-open', shrunk);
});
}
body.keyboard-open .bottom-nav { display: none; }
The 120px threshold matters. A smaller difference is Safari’s own toolbar collapsing, not a keyboard, and reacting to that makes the bar flicker on every scroll.
Content hidden behind the bar
A fixed bar is out of the document flow, so the last part of every page renders underneath it. The bottom of your content, the final paragraph, the submit button on a form.
Reserve the space with padding that accounts for both the bar and the safe area:
body {
padding-bottom: calc(64px + env(safe-area-inset-bottom, 0px));
}
If the bar can hide, that padding has to change with it, or you get a gap where the bar used to be. Keeping the height in a CSS variable and updating one value is far less painful than chasing hard-coded numbers through a stylesheet.
Worth knowing: a service worker that calls clients.claim() fires a controllerchange event the first time it installs. If your code reloads the page on that event to pick up updates, it will reload on a visitor’s very first page view, which looks exactly like a layout bug and can interrupt anything mid-flight. Only reload when a controller already existed when the page loaded.
Getting this right without writing it yourself
Every one of these is solvable in an afternoon. The difficulty is that none of them are obvious, they only appear on real devices, and Safari gives no errors for any of them. Most of the time spent on a WordPress PWA bottom nav goes on discovering the problem, not fixing it.
If you are running this on shared hosting, the resource limits behind most random outages are worth knowing about too, and connecting an AI assistant to WordPress covers the other half of the toolkit.
Marketur PWA PowerUp
Installable progressive web app for WordPress, with app-style bottom navigation, splash screens, offline handling and push notifications. The safe area handling, keyboard detection and service worker behaviour described above are built in and tested on real devices.