Fix 100vh Issues on Mobile Safari (WordPress-Friendly Solutions)

January 1, 2026
Fix 100vh Issues on Mobile Safari (WordPress-Friendly Solutions)

On iOS Safari, 100vh often behaves differently than expected because the browser UI (address bar / toolbar)
expands and collapses while scrolling. The result is a hero section that’s too tall, too short, or “jumps” when the UI changes.

This is a common source of layout shift (CLS) and broken full-height sections in WordPress themes—especially for hero headers,
mobile menus, and fullscreen modals.

Why 100vh Breaks on iOS Safari

Historically, mobile Safari calculated vh based on a “visual viewport” that didn’t consistently reflect the
currently visible area when the address bar is shown/hidden. So:

  • height: 100vh can include space behind the browser UI
  • Sections can overflow and force unwanted scrolling
  • Heights can change mid-scroll, causing visible jumps

Recommended Strategy

Use a layered approach:

  • Prefer modern viewport units (svh, dvh, lvh) when available
  • Provide a safe fallback for older iOS via CSS + optional tiny JS
  • Keep it WordPress-friendly: enqueue a small script only when needed

Solution 1: Use Modern Viewport Units (Best When Supported)

Modern CSS viewport units:

  • 100svh: “small viewport height” (when UI is visible) → stable, avoids overflow
  • 100dvh: “dynamic viewport height” (updates with UI changes) → matches visible area but can resize while scrolling
  • 100lvh: “large viewport height” (when UI is hidden) → can be too tall at first

For most hero sections, svh is the safest default because it avoids content being pushed behind Safari UI.
For true fullscreen overlays, dvh is usually the best match.

Hero Section: Stable Height (Avoid Jumping)

/* Stable hero: avoids iOS Safari overflow issues */
.hero {
  min-height: 100vh;
  min-height: 100svh;
}

Fullscreen Overlay / Mobile Menu: Match Visible Area

/* Overlay wants the visible viewport, even as UI changes */
.overlay {
  height: 100vh;
  height: 100dvh;
}

Using both lines is intentional: older browsers use 100vh, modern ones override with svh/dvh.

Solution 2: iOS Fallback with CSS Variable (Most Reliable Cross-Version)

If you need consistent behavior on older iOS Safari versions, set a CSS custom property based on the real viewport height.
This is the most reliable “no-surprises” fix for full-height sections.

CSS: Use a Custom Property as the Height Source

:root {
  --vh: 1vh;
}

/* Use calc(var(--vh) * 100) instead of 100vh */
.hero {
  min-height: calc(var(--vh) * 100);
}

JavaScript: Set –vh to window.innerHeight * 0.01

This script is small and safe. It updates on resize and orientation changes.

(function () {
  function setVh() {
    var vh = window.innerHeight * 0.01;
    document.documentElement.style.setProperty('--vh', vh + 'px');
  }

  setVh();

  window.addEventListener('resize', setVh, { passive: true });
  window.addEventListener('orientationchange', setVh, { passive: true });
})();

Why this works: window.innerHeight reflects the visible viewport height more accurately on mobile Safari.

Solution 3: Use -webkit-fill-available (Useful as a Fallback)

Some iOS Safari versions support -webkit-fill-available. It’s not a complete replacement for the modern viewport units,
but it can help in specific layouts.

/* Often used for iOS Safari fallback */
.hero {
  min-height: 100vh;
  min-height: -webkit-fill-available;
}

This is best treated as an additional fallback, not your primary solution.

WordPress-Friendly Implementation

If your theme only needs the JS-based fix on specific templates (hero landing page, fullscreen menu, modal),
enqueue it conditionally.

functions.php: Enqueue Script Only Where Needed

<?php
add_action( 'wp_enqueue_scripts', function () {
  if ( is_admin() ) {
    return;
  }

  // Example: only on the front page or a landing page template
  if ( is_front_page() || is_page_template( 'templates/landing.php' ) ) {
    wp_enqueue_script(
      'vh-fix',
      get_template_directory_uri() . '/assets/js/vh-fix.js',
      array(),
      '1.0.0',
      true
    );
  }
}, 20 );

Then place the JS snippet into assets/js/vh-fix.js.

Which Solution Should You Choose?

  • If you target modern browsers: use 100svh (hero) and 100dvh (overlays)
  • If you need broad iOS coverage: use the CSS variable (--vh) approach
  • If you want extra safety: add -webkit-fill-available as a fallback

Common Pitfalls

  • Using only height: 100vh for mobile menus (causes overflow behind Safari UI)
  • Using 100dvh for hero sections and then complaining about “jumping” during scroll
  • Applying the JS fix globally (unnecessary overhead). Scope it to pages that need it
  • Forgetting min-height vs height (heroes often work better with min-height)

Practical Patterns (Copy/Paste)

Stable Hero (Recommended Default)

.hero {
  min-height: 100vh;
  min-height: 100svh;
}

Fullscreen Overlay (Modal / Mobile Nav)

.overlay {
  height: 100vh;
  height: 100dvh;
}

Maximum Compatibility (Hero + JS Variable)

:root { --vh: 1vh; }

.hero {
  min-height: 100vh;
  min-height: 100svh;
  min-height: calc(var(--vh) * 100);
}

Summary

  • Mobile Safari makes 100vh unreliable due to dynamic browser UI
  • Use 100svh for stable heroes and 100dvh for true fullscreen overlays
  • For older iOS, the most reliable fix is a CSS variable set via window.innerHeight
  • In WordPress, enqueue the JS only on templates/pages that need it

With these patterns, you can eliminate “jumping” full-height sections on iOS Safari and keep your WordPress theme
both stable and performance-friendly.

Avatar

Written by

satoshi

I’ve been building and customizing WordPress themes for over 10 years. In my free time, you’ll probably find me enjoying a good football match.