CSS Dynamic Viewport Units Table

UnitWhat it measuresField note
vh / vw1% of the initial viewportThe classic - 100vh is the LARGE viewport, URL bar hidden
dvh (dynamic)Viewport RIGHT NOWShrinks/grows as the URL bar shows/hides - the honest one
svh (small)Smallest possibleThe URL-bar-visible size - content always fits, may leave space
lvh (large)Largest possibleThe URL-bar-hidden size - content may hide under the bar
100vh mobile bugThe reason dvh exists100vh = lvh on mobile - bottom 60-100px hide under the URL bar
dvh jank tradeIt resizes per scrollEvery bar show/hide resizes - animate costs, stable UIs pick svh
svh for floorsmin-height: 100svhHero always fully visible - no cut-off CTA under the bar
Fallback patternvh then dvhheight: 100vh; height: 100dvh - old browsers keep the classic
Reference: the MDN viewport units reference. The mobile 100vh bug has a name now: 100vh measures the LARGE viewport (URL bar hidden), so on phones the bottom 60-100 pixels of a 100vh hero hide under the visible URL bar. The dynamic viewport unit dvh tracks the viewport RIGHT NOW - shrinking and growing as the bar shows and hides - while svh (small, always-fits) and lvh (large) pin the extremes. Bottom line: min-height: 100svh for heroes (fully visible, guaranteed), dvh for full-bleed apps (honest, but it resizes per scroll - jank-aware), and ship the fallback pair: height: 100vh then height: 100dvh. Related tools: visual viewport table (the JS-side view and pinch zoom), clamp table (dvh inside fluid sizing), and matchMedia table (orientation and breakpoint partners).

The mobile 100vh bug finally has a name: 100vh measures the LARGE viewport (URL bar hidden), so on phones the bottom 60-100 pixels of a 100vh hero hide under the visible URL bar. The dynamic viewport units fix it by naming which viewport you mean.

Bottom line: dvh tracks the viewport RIGHT NOW (shrinking and growing as the URL bar shows and hides - honest but resize-happy), svh is the smallest possible (content always fits), lvh the largest. The decision rule: min-height: 100svh for heroes (fully visible, guaranteed), dvh for full-bleed app shells, and always ship the fallback pair - height: 100vh then height: 100dvh.

The honest part: dvh resizes on every URL-bar show/hide - meaning layout work during scroll on some browsers. Stable heroes pick svh precisely because it never moves; dvh is for UIs that must track the true visible area continuously.

How to use

  1. Hero that always fits: min-height: 100svh - the small viewport is the guarantee: the whole hero is visible no matter how the URL bar sits.
  2. Full-bleed app shell: height: 100dvh on the app root - it tracks the real visible area as bars appear and disappear.
  3. Ship the fallback pair: height: 100vh; height: 100dvh; - old browsers keep the classic vh, new browsers get the honest dynamic value.

Frequently asked questions

Why does 100vh cover more than the visible screen on phones?

Historical definition: vh measures the viewport when DYNAMIC TOOLBARS ARE HIDDEN - the largest state. Mobile browsers shrink and hide the URL bar during scroll, so the visible area oscillates between the small viewport (bar shown) and the large one (bar hidden) - and 100vh was pinned to the LARGE one. The result: a 100vh section is taller than the visible screen whenever the bar is showing, cutting off anything anchored to its bottom (a CTA button, a form submit). Every 'my hero button is half-hidden on iPhone' bug is this unit definition, not a layout mistake.

When do I pick dvh versus svh versus lvh?

By stability versus honesty. dvh (dynamic) tracks the viewport right now - the true visible area at all times, which is what full-bleed app shells and bottom-sheet-adjacent layouts want; its cost is resizing during scroll as the URL bar appears, which is visual jank on animation-heavy pages. svh (small) is the always-fits guarantee: size to the smallest the viewport can be, and content is never cut - heroes, full-screen sections, anything with a bottom CTA. lvh (large) matches the old 100vh - useful when content should extend under the retracting bar. The default recommendation: svh for sections with anchored bottoms, dvh for app shells, lvh only to reproduce legacy behavior deliberately.

What is the fallback pattern for browsers without dvh?

Declaration pairs, ordered by cascade. Declare height: 100vh first, then height: 100dvh: browsers that understand dvh use it (the later declaration wins), older browsers ignore it as invalid and keep the vh value - the classic progressive-enhancement double declaration, no @supports needed. The same pair pattern applies to min-height: 100svh fallbacks (100vh, then 100svh) and any mixed use. One caution: old browsers fall back to the LARGE viewport measurement, so their heroes still hide under the bar - the fallback is 'works as before', not 'works perfectly everywhere'; supporting those browsers fully means layout that tolerates the bar's overlap regardless of units.

How do viewport units interact with the keyboard and visual viewport?

They answer different questions. vh/dvh/svh/lvh all measure the LAYOUT viewport region (with dynamic variants tracking toolbar changes) - none of them account for the on-screen KEYBOARD, which on most mobile browsers overlays the page without resizing the layout viewport. When the keyboard matters (chat inputs, form screens), the visualViewport API is the instrument: its height reports the actual visible area under the keyboard, and interactive-widget=resizes-content can opt the layout into resizing. The unit choice (dvh vs svh) handles the URL bar dimension; the keyboard dimension needs the API - the two problems look similar and have different tools.

Related tools