WordPress | August 24, 2026

Fixed Bottom Navigation in a WordPress PWA (Including the iOS Fix)

A complete guide to fixed bottom navigation on WordPress and WordPress PWAs: markup, CSS, safe-area handling, the iOS standalone quirks, accessibility and testing.

By Marketur

Quick answer

Add a nav element with position: fixed and bottom: 0, pad it with env(safe-area-inset-bottom), set viewport-fit=cover in your viewport meta tag, and add matching bottom padding to your page content. The iOS standalone quirks and full code are below.

Fixed Bottom Navigation in a WordPress PWA (Including the iOS Fix)

Fixed bottom navigation is the bar of icons pinned to the bottom of the screen, the same pattern native apps use. On WordPress it is a few lines of HTML and CSS. On a WordPress site installed as a PWA, especially on an iPhone, it is a few lines of HTML and CSS plus three specific fixes nobody warns you about. This tutorial covers all of it, with code you can paste.

What fixed bottom navigation is, and when to use it

Fixed bottom navigation is a bar that stays glued to the bottom of the viewport while the page scrolls behind it. It exists because of thumbs: on a phone, the bottom of the screen is the easiest place to reach, so the main destinations live there.

It makes sense when your site behaves like an app: people come back often, and there are three to five primary destinations they bounce between. Think memberships, dashboards, shops, communities.

It usually does not make sense for a pure content site or blog. If visitors mostly read and leave, a permanent bar just steals vertical space. Be honest about which one you are building before you add it.

Why it breaks on phones

Three things conspire against a fixed bottom bar on mobile:

  1. The iOS home indicator. Modern iPhones have no home button. There is a swipe zone at the bottom of the screen and Safari reserves it. Your fixed bar renders underneath that zone unless you handle the safe area.
  2. The viewport meta tag. The CSS function env(safe-area-inset-bottom) only returns a real value when your viewport meta includes viewport-fit=cover. Without it the value is always 0, and your careful padding silently does nothing.
  3. Standalone PWA mode. When someone installs your site to their home screen, it runs without browser chrome and the viewport behaves slightly differently than the tab you tested in. This is where most "it worked in the browser" reports come from.

Every fix below maps to one of these three.

The HTML structure

Keep it to three to five destinations, each with an icon and a short label:

<nav class="mk-bottom-nav" aria-label="Primary">
  <a href="/" aria-current="page">
    <!-- your icon svg here -->
    <span>Home</span>
  </a>
  <a href="/blog/">
    <!-- your icon svg here -->
    <span>Blog</span>
  </a>
  <a href="/shop/">
    <!-- your icon svg here -->
    <span>Shop</span>
  </a>
  <a href="/account/">
    <!-- your icon svg here -->
    <span>Account</span>
  </a>
</nav>

The nav element and aria-label make it a proper landmark for screen readers. aria-current="page" marks the active destination.

The CSS

This is the complete pattern, safe area included:

/* The bar itself */
.mk-bottom-nav {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 9999;
  display: flex;
  min-height: 56px;
  /* Extend the bar's background into the iPhone home indicator area */
  padding-bottom: env(safe-area-inset-bottom, 0px);
  background: #111111;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
}

.mk-bottom-nav a {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  min-height: 56px; /* comfortably above the 44px minimum touch target */
  color: #eeeeee;
  text-decoration: none;
  font-size: 11px;
}

/* Only show it on phones */
@media (min-width: 768px) {
  .mk-bottom-nav {
    display: none;
  }
}

/* Stop the bar from covering your content */
body {
  padding-bottom: calc(56px + env(safe-area-inset-bottom, 0px));
}

@media (min-width: 768px) {
  body {
    padding-bottom: 0;
  }
}

Two details matter here:

  • Use padding on the bar, not margin. Margin leaves a transparent strip under the bar where page content shows through the home indicator area. Padding makes the bar's own background fill that strip, which is what native apps do.
  • The body padding mirrors the bar's total height. Without it, the last line of every page hides behind the bar and users cannot scroll it into view.

The viewport meta tag

Check the head of your site for this line:

<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">

The critical part is viewport-fit=cover. It tells iOS your page is allowed to draw edge to edge, which is what makes env(safe-area-inset-bottom) return a real number.

WordPress core does not print a viewport meta tag; your theme does, usually in header.php. Search your theme files for "viewport" and add viewport-fit=cover to the content attribute. On a classic theme, make the change in a child theme so updates do not wipe it. You can output a second viewport tag from a plugin via wp_head, but browsers merge duplicate viewport tags unpredictably, so editing the original is the clean fix.

Adding it to WordPress

Option 1: A tiny plugin

The most portable approach. Create a folder in wp-content/plugins, drop this file in, and activate it:

<?php
/**
 * Plugin Name: Fixed Bottom Nav
 * Description: Adds a fixed bottom navigation bar on small screens.
 */

add_action('wp_footer', function () {
    if (is_admin()) {
        return;
    }
    ?>
    <nav class="mk-bottom-nav" aria-label="Primary">
        <a href="<?php echo esc_url(home_url('/')); ?>">Home</a>
        <a href="<?php echo esc_url(home_url('/blog/')); ?>">Blog</a>
        <a href="<?php echo esc_url(home_url('/shop/')); ?>">Shop</a>
        <a href="<?php echo esc_url(home_url('/account/')); ?>">Account</a>
    </nav>
    <?php
});

add_action('wp_head', function () {
    if (is_admin()) {
        return;
    }
    echo '<style>/* paste the CSS from the previous section here */</style>';
});

This uses two standard WordPress hooks: wp_head prints the styles into the document head, and wp_footer prints the markup right before the closing body tag, so it works with almost any theme. For a production site, move the CSS into a real stylesheet and load it with wp_enqueue_style instead of inlining it.

Option 2: A block theme template part

On a block theme, open Appearance, then Editor, and add the navigation markup to your footer template part so it renders on every page. No PHP required, but the markup lives in that theme, so switching themes takes the bar with it.

Option 3: Use an existing plugin

If you never want to touch code, search wordpress.org for a mobile bottom navigation plugin (Fly Nav Mobile is one example). You give up control over the safe-area details, so verify the result on a real iPhone before trusting it.

The iOS standalone PWA fixes

This is the section that existed in the original version of this article, because it is where everyone gets stuck.

First, your web app manifest needs standalone display:

{
  "display": "standalone"
}

Then the three fixes:

  • The safe area only works with viewport-fit=cover. Already covered above, but it causes half of all "my padding does nothing" reports, so check it first.
  • The white gap below the bar. If any part of your layout uses height: 100vh, mobile browsers measure vh against a viewport that ignores collapsing browser chrome, and things overflow or leave gaps in standalone mode. Use dvh (dynamic viewport height) with a vh fallback:
.full-height {
  min-height: 100vh; /* fallback for older browsers */
  min-height: 100dvh;
}
  • Overscroll shows white behind the bar. When a user rubber-band scrolls past the bottom, iOS reveals whatever is behind your page. Give html and body a background color that matches the bar so the bounce looks intentional.

And the golden rule: test in the installed app, not the browser tab. Add to Home Screen, then open your site from the icon. The standalone bugs do not exist in the tab, so testing there proves nothing.

Optional: hide the bar while scrolling down

App-style bars often slide away as you scroll down and return when you scroll up. A small vanilla script does it:

let lastY = window.scrollY;
const nav = document.querySelector('.mk-bottom-nav');

window.addEventListener(
  'scroll',
  function () {
    if (!nav) return;
    const y = window.scrollY;
    if (y > lastY && y > 120) {
      nav.classList.add('mk-bottom-nav--hidden');
    } else {
      nav.classList.remove('mk-bottom-nav--hidden');
    }
    lastY = y;
  },
  { passive: true }
);
.mk-bottom-nav {
  transition: transform 0.2s ease;
}

.mk-bottom-nav--hidden {
  transform: translateY(100%);
}

@media (prefers-reduced-motion: reduce) {
  .mk-bottom-nav {
    transition: none;
  }
}

The prefers-reduced-motion block is not optional decoration. Some users ask their phone to reduce motion, and sliding chrome is exactly the kind of movement that bothers them.

Accessibility checklist

  • Use a real nav element with an aria-label, not a pile of divs.
  • Mark the current destination with aria-current="page".
  • Keep every touch target at least 44px tall. The 56px minimum height in the CSS above covers this.
  • Check icon and label contrast against the bar background.
  • Do not let the bar steal focus order surprises: it sits at the end of the document, which is where keyboard and screen reader users will meet it.

Common mistakes

  • Forgetting viewport-fit=cover, then wondering why env() always returns 0.
  • Using margin-bottom instead of padding-bottom on the bar, leaving a transparent gap under the home indicator.
  • No bottom padding on the body, so the last line of every page hides behind the bar.
  • Cramming in six or seven destinations. Keep it to three to five; the rest belongs in a menu.
  • z-index wars with sticky headers, cookie banners and chat widgets. Set a deliberate stacking order once.
  • Testing only in DevTools device emulation. The iOS standalone bugs only appear after Add to Home Screen.

Testing checklist

  1. Chrome DevTools device toolbar for basic layout and breakpoint checks.
  2. A real iPhone in a Safari tab: check the home indicator overlap.
  3. The same iPhone, installed via Add to Home Screen: check the standalone quirks.
  4. Android Chrome, installed to the home screen: usually easier, but verify anyway.
  5. Scroll to the very bottom of your longest page and confirm the final content clears the bar.

Frequently asked questions

Does a fixed bottom bar hurt SEO?

No. It is ordinary HTML and CSS, and Google indexes your content the same either way. What matters is that the bar never covers your content on mobile, because a broken mobile experience is a quality problem even when it is not a ranking penalty.

How many items should bottom navigation have?

Three to five. Fewer than three means the bar probably should not exist. More than five and labels become unreadable and taps become error-prone.

Does this work with the WordPress admin bar?

The admin bar is fixed to the top, so the two never collide directly. But while you are logged in, the visible screen is shorter and any sticky elements stack up. Test once while logged in as an admin, and remember that regular visitors never see the admin bar at all.

If this PWA is a business, not just a project

We know these details because Marketur itself is an installable app that ships exactly this pattern: fixed bottom navigation, safe-area handling, hide on scroll. The tutorial above is the same playbook.

If the site behind your navigation bar is meant to make money, the bar is the easy part. The hard part is whether anyone needs what is behind it. Marketur's Reality Check pressure-tests the idea, and Find My Customers works out where your users actually are, before you spend months building.

Share this article