Why this exists

min-height: 100vh is one of the most common lines in any hero section's CSS, and on desktop it does exactly what it looks like it does - fill the screen. On mobile Safari, and to a lesser extent mobile Chrome, it doesn't. The unit isn't buggy in the sense of behaving randomly; it's doing exactly what the spec says. The spec just doesn't mean what most people assume it means, and that gap is what causes a hero's call-to-action button to sit just below the fold, or a page to visibly jump the instant someone starts scrolling.

What 100vh actually measures

On a phone, the browser chrome - the address bar, the tab strip, the bottom toolbar - takes up real space, and it isn't static. It collapses when you scroll down and reappears when you scroll back up or land on a fresh page. That means the actual visible area of the page keeps changing size by the height of that chrome, often 60-100px, without the page itself resizing.

100vh doesn't track that. It's defined against the largest possible viewport - the one with the browser chrome fully collapsed - and it stays pinned to that value regardless of whether the chrome is actually showing right now. On first load, with the address bar visible, an element set to min-height: 100vh is taller than the space actually available, so the bottom of it - often exactly the button or headline meant to close out the hero - sits below what's currently on screen.

What it actually looks like

  • A hero section's CTA button sitting just out of view on first load, even though the design clearly intends it to be visible.
  • The page visibly resizing the moment a user starts scrolling, as the address bar collapses and the browser re-evaluates layout against a viewport that's now a different size than it computed 100vh against.
  • The same page looking correct in desktop Chrome dev tools' mobile emulation, because emulated viewports don't reproduce the collapsing-chrome behavior of a real device.

That last point is what makes this easy to ship without noticing - the bug is specific to real mobile browsers with dynamic chrome, not to narrow viewport width, so a resized desktop browser window won't reproduce it.

The decision log

Decision 1: Confirm it's the viewport unit, not application CSS

Before touching anything, the fix has to match the actual cause. Cutting the affected section's own padding, margin, and flex rules down to the minimum and testing on a real device - not an emulator - confirmed the section itself was fine; only elements using vh units were affected.

Decision 2: Reject the JavaScript viewport-height polyfill

The long-standing fix for this is a small script that reads window.innerHeight, writes it to a CSS custom property, and re-runs on every resize event, with every 100vh reference swapped for calc(var(--vh, 1vh) * 100). It works, but it adds a runtime dependency and a resize listener to fix something CSS can now handle natively, and it still renders wrong for one frame before the script runs.

Decision 3: Use svh as a fallback, not a replacement

Modern browsers support svh (small viewport height) - the viewport size with browser chrome visible, which is the value that was actually needed all along. Replacing 100vh outright would break older browsers that don't recognize the unit and would simply drop the declaration. Declaring 100vh first and 100svh immediately after solves that: browsers that understand svh use it, since it's the later declaration for the same property; browsers that don't just ignore that line and keep the vh value.

Decision 4: Apply it everywhere, not just the one hero that got reported

A single fixed hero doesn't help much if the same min-height: 100vh pattern is copy-pasted across every other page's hero section too. The actual fix was an audit - every occurrence of the raw property, across the shared stylesheet and every page-specific override, got the same two-line treatment, not just the page someone happened to be looking at when this came up.

Before and after

Before

  • min-height: 100vh computed against the largest possible viewport
  • Hero content extending below what's actually visible on first load
  • Layout visibly resizing as the address bar collapses on scroll
  • Bug invisible in desktop responsive-mode emulation

After

  • min-height: 100svh declared right after the vh value, same property
  • Modern browsers size against the chrome-visible viewport correctly
  • Older browsers silently keep the original vh behavior
  • No JavaScript, no resize listener, no polyfill

What this actually took

One extra line per rule:

min-height: 100vh; min-height: 100svh;

The fix itself is trivial. What took the actual time was finding every place the pattern already existed - a shared stylesheet rule, plus a handful of page-specific overrides that had been copy-pasted with their own min-height: 100vh - and confirming each one got the same treatment, rather than fixing the one hero someone happened to notice and leaving the rest inconsistent. Catching mobile-specific rendering issues like this one is a standard part of the Web & Digital work done on every build.

Frequently asked questions

What's the actual difference between vh, svh, lvh, and dvh?

vh is defined inconsistently across mobile browsers in practice, usually against the largest viewport. svh (small viewport height) is the viewport with browser chrome fully visible - the safe, conservative value. lvh (large viewport height) is the viewport with chrome fully collapsed - functionally close to how vh already behaves on mobile. dvh (dynamic viewport height) tracks the chrome in real time and resizes as it collapses or reappears, which fixes the cutoff but reintroduces the layout-shift-on-scroll behavior this fix is meant to avoid. svh is the one that actually solves the "content cut off" problem without adding movement.

Do I still need a JavaScript viewport-height polyfill?

No, for any browser that supports svh - which is every current major mobile browser. The fallback declaration handles the rest: browsers without svh support simply keep using vh, the same way they already were before this fix existed.

Does this affect desktop browsers at all?

No. Desktop browsers don't have collapsing chrome that changes the viewport size, so vh and svh resolve to the same value there. The fallback declaration is inert on desktop - it's purely a mobile fix, with no visible change anywhere else.

Not sure what your own site does on a real phone?

Desktop responsive mode misses this one. A quick look on an actual device is usually enough to tell.