On this page
Dynamic viewport units (dvh, svh, lvh)
On mobile, 100vh is taller than the screen because it ignores the browser's collapsing toolbar. Use dvh, svh, and lvh to size full-height elements to the viewport that is actually visible.
What it is
dvh, svh, and lvh are viewport-relative length units that describe a height the way vh always promised to but never delivered on mobile. They come with vw siblings (dvw, svw, lvw) and a matching set for the inline and block axes (vi, vb).
The three height units differ in which mobile viewport they measure:
lvh- the large viewport: the screen with the browser’s URL bar and toolbar retracted.100lvhis the same as the legacy100vh.svh- the small viewport: the screen with that chrome fully shown.100svhis always the safe maximum that fits without scrolling.dvh- the dynamic viewport: whatever is visible right now. It updates live as the toolbar slides in and out while the user scrolls.
.hero { min-height: 100svh; } /* never overflows */
.sheet { max-height: 100dvh; } /* tracks the toolbar live */
Why it matters
On a phone, the browser’s toolbar collapses as you scroll down and reappears as you scroll up, so the visible height changes during a single page view. The original vh unit resolved to the largest possible height. An element set to height: 100vh is therefore taller than the screen whenever the toolbar is showing - its bottom edge sits behind the URL bar, hiding a “Continue” button, cutting off a full-screen menu, or leaving a strip that only appears after scrolling.
The dynamic units fix this without JavaScript. For years the workaround was a resize listener writing a --vh custom property on every frame - janky, and a needless main-thread cost that works against Core Web Vitals. The units replace all of it. They are Baseline and supported in every current browser engine.
How to implement
- For an element that must always fit on first paint - a landing hero, a login screen, a modal - use
svh. It is the conservative choice that never overflows, in any toolbar state. - For an element that should track the visible area as the chrome moves - a bottom sheet, a slide-out menu, an overlay’s max-height - use
dvh. - Use
lvhonly when you deliberately want the retracted-toolbar height; it behaves like the oldvh. - Keep a
vhfallback first if you support very old browsers:height: 100vh; height: 100dvh;. The cascade ignores the unit it doesn’t understand.
This site ships it: the ⌘K search overlay and the mobile navigation menu are capped with dvh in src/styles/global.css, so neither extends behind the address bar on a phone.
Common mistakes
- Animating
dvh-sized layout. Becausedvhchanges as the toolbar slides, an element whoseheightis indvhreflows continuously during the scroll, which can look jittery and shift content. For large always-on regions in the scroll flow, prefersvh; reservedvhfor elements where live tracking is the point. (Capping a modal or menu withmax-height: …dvhis safe - it’s only an upper bound, and an open<dialog>locks background scrolling, so the toolbar isn’t moving while it shows. That’s how this site uses it.) - Using
100vhfor a hero and assuming it fits. It does on desktop and lies on mobile. Switch tosvh. - Reaching for a JavaScript
--vhhack. That pattern predates these units and is now pure technical debt.
CSS Values Level 4 Viewport Units & Mobile Browser Chrome Behavior
Handling responsive vertical layouts across mobile WebKit and Blink browser engines requires using CSS Values Level 4 viewport units:
- Small (
svh), Large (lvh), and Dynamic (dvh) Viewport Boundaries: On mobile devices, browser UI chrome (address bar, tab navigation bar) expands and contracts dynamically during scrolling.100svhmeasures the viewport assuming UI chrome is fully expanded (guaranteeing zero vertical overflow).100lvhmeasures the viewport when UI chrome is fully collapsed (equivalent to legacy100vh).100dvhupdates dynamically as UI chrome collapses. - Replacing Legacy JavaScript
--vhHacks: Legacy workarounds that boundwindow.innerHeightresize listeners to CSS custom variables (--vh) cause main-thread layout thrashing and trigger Cumulative Layout Shift (CLS). Replacing scripted height calculations with nativesvhordvhCSS declarations offloads viewport calculation to the browser engine. - Fallback Syntax Cascading: Provide progressive enhancement fallbacks for legacy browsers by cascading property declarations (
height: 100vh; height: 100svh;).
Verification
- On a real phone, scroll a page with a
100svhand a100vhsection. Thevhone is cut off at the bottom while the toolbar shows; thesvhone is not. - In Chrome or Safari device emulation, toggle the simulated toolbar and confirm
dvh-sized elements resize whilelvh-sized ones stay fixed.