Stabilize your mobile web layouts in 60 seconds. Solve jumping bottom menus, broken popups, and the "100vh lie" on iOS Safari, Chrome, and Firefox using modern CSS Dynamic Viewport units (dvh) and a robust shell architecture.
#iOS #Safari #NextJS #TailwindCSS #WebDev #PWA #MobileUX #Frontend
A professional, drop-in solution for Next.js 15+ and Tailwind CSS 4+ projects to solve "jumping" layouts, detached bottom menus, and broken popups on iOS browsers (Safari, Chrome, Firefox).
Add viewportFit: 'cover' to your viewport config and wrap your app in the Flexbox Shell.
// app/layout.tsx
import { Viewport } from "next";
export const viewport: Viewport = {
width: "device-width",
initialScale: 1,
viewportFit: 'cover', // Required to handle iPhone notches and toolbars
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
{/*
h-dvh: Dynamic Viewport Height (responsive to keyboard)
overflow-hidden: Prevents the entire page from scrolling/bouncing
*/}
<body className="h-dvh overflow-hidden flex flex-col bg-white">
{/* FIXED HEADER */}
<header className="flex-none p-4 border-b">Header (Static)</header>
{/* SCROLLABLE CONTENT (The Magic Box) */}
<div className="flex-1 overflow-y-auto overscroll-contain [WebkitOverflowScrolling:touch]">
<main className="min-h-full">
{children}
</main>
<footer className="p-8 border-t bg-zinc-50">Footer (Inside Scroll)</footer>
</div>
</body>
</html>
);
}Lock the body height and disable the "rubber-banding" effect.
/* globals.css */
@import "tailwindcss";
html, body {
max-width: 100dvw;
overflow-x: hidden;
width: 100%;
}
body {
/* h-dvh adapts to the virtual keyboard and Safari toolbars! */
height: 100dvh;
overflow: hidden;
position: relative;
overscroll-behavior: none; /* Disables page bounce */
}On iOS Safari, the browser toolbars are dynamic. When you use 100vh, the UI often gets "pushed" or buttons at the bottom become unclickable/hidden under the toolbar.
This solution provides:
- No Layout Jumps: When the keyboard opens, the
dvhunit recalculates, and only the internal content area shrinks. Headers stay at the top, and bottom-docked elements stay visible. - Safe Area Support: Uses
viewportFit: 'cover'to ensure your app background covers the area behind the notch. - Native Feel: Disables the annoying "page bounce" (rubber-banding) using
overscroll-behavior: none. - Universal Compatibility: Works on Safari, Chrome, and Firefox for iOS (all use WebKit).
- Next.js 15+
- Tailwind CSS 4+
- TypeScript 5+
MIT