You set height: 100vh on a hero section, test it on desktop, and everything looks perfect. Then you open it on your phone: the bottom of your section hides behind the browser's address bar. This is the single most common viewport bug in CSS, and it has been frustrating developers since mobile browsers started dynamically resizing their UI.
- Use
height: 100dvhinstead of100vhfor full-screen sections on mobile - Fall back to
100vhfor older browsers:height: 100vh; height: 100dvh; - Avoid
dvhon elements that repaint often, as it triggers layout recalculations on scroll
What Are vh and vw Units?
vh and vw are viewport-percentage length units. 1vh equals 1% of the viewport height, and 1vw equals 1% of the viewport width. They let you size elements relative to the browser window rather than a parent container.
.hero {
height: 100vh; /* full viewport height */
width: 100vw; /* full viewport width */
}On desktop, this works exactly as expected. The problem starts on mobile devices.
The 100vh Mobile Bug: Why vh Breaks on Phones
Mobile browsers like Chrome and Safari have a dynamic toolbar: the address bar and bottom navigation bar expand and collapse as the user scrolls. When the page first loads, the toolbar is visible, making the actual visible area shorter than what 100vh reports.
Here is what happens step by step:
- User opens the page. The address bar is visible.
- The browser sets
100vhto the largest possible viewport (toolbar hidden). - Your
100vhelement overflows the visible screen by the height of the toolbar. - The user sees a scrollbar or a cut-off section.
Between 2015 and 2016, Safari and Chrome engineers deliberately made 100vh equal to the viewport with the toolbar retracted. This prevented jarring layout jumps during scroll, but it introduced the overflow problem that still catches developers off guard today.
The Old Workarounds
Before the new viewport units landed, developers used two common hacks:
JavaScript approach:
function setVh() {
document.documentElement.style.setProperty(
'--vh', ${window.innerHeight * 0.01}px
);
}
window.addEventListener('resize', setVh);
setVh();.hero {
height: calc(var(--vh, 1vh) * 100);
}WebKit-only CSS fix:
.hero {
height: 100vh;
height: -webkit-fill-available;
}Both approaches were fragile. The JavaScript version added a resize listener and caused layout thrashing. The -webkit-fill-available trick only worked in Safari and had inconsistent behavior across versions.
I ran into the -webkit-fill-available problem while building a landing page that needed pixel-perfect full-screen sections on both iOS and Android. Safari rendered it correctly, but Chrome on Android ignored the property entirely, leaving a visible gap at the bottom. Switching to dvh with a vh fallback fixed both platforms in a single declaration.
dvh, lvh, and svh: The Modern Fix
The CSS Working Group introduced three new viewport unit families in 2022 to solve this problem properly. All major browsers now support them: Chrome 108+, Firefox 101+, Safari 15.4+.
svh (Small Viewport Height)
svh measures the viewport when the browser UI takes up the most space, meaning all toolbars are visible. 1svh = 1% of the smallest possible viewport height.
Use svh when you need content to fit without any overflow on initial page load:
.hero {
height: 100svh;
}This guarantees your hero section never gets clipped by the address bar.
lvh (Large Viewport Height)
lvh measures the viewport when the browser UI takes up the least space, meaning toolbars are hidden or collapsed. 1lvh = 1% of the largest possible viewport height.
In practice, lvh behaves almost identically to the traditional vh unit. Use it when you want explicit control and clarity in your code:
.background-image {
min-height: 100lvh;
}dvh (Dynamic Viewport Height)
dvh is the unit that actually adapts in real time. When the toolbar is visible, 100dvh equals 100svh. When the toolbar retracts, 100dvh equals 100lvh. It transitions smoothly between the two states.
.fullscreen-section {
height: 100dvh;
}When to use dvh: Full-screen hero sections, splash screens, modal overlays, and any element that must fill exactly the visible area at all times.
When NOT to use dvh: Elements that repaint frequently or contain complex animations. Because dvh triggers layout recalculations every time the toolbar state changes, it can cause performance issues. The CSS spec itself warns: "Using these units can cause content to resize while the user scrolls the page. Depending on usage, this can be disturbing to the user and/or costly in terms of performance."
Quick Comparison: vh vs svh vs lvh vs dvh
| Unit | Viewport State | Value on Mobile (toolbar visible) | Value on Mobile (toolbar hidden) | Recalculates on Scroll? |
|---|---|---|---|---|
vh | Large (fixed) | Too tall, overflows | Correct | No |
svh | Small (fixed) | Correct | Slightly short | No |
lvh | Large (fixed) | Too tall, overflows | Correct | No |
dvh | Dynamic | Correct | Correct | Yes |
On desktop browsers where the toolbar does not resize, all four units return the same value.
Width Variants: vw, svw, lvw, dvw
The same logic applies horizontally. While less common (most mobile browsers do not have side-collapsing UI), the full set of width units exists:
svw/lvw/dvwfor widthsvi/lvi/dvifor inline axis (respects writing direction)svb/lvb/dvbfor block axissvmin/svmax/lvmin/lvmax/dvmin/dvmaxfor min/max of width and height
In most real-world projects, you will use the height variants (dvh, svh, lvh) far more often than the width ones.
Practical Patterns: When to Use Which Unit
Full-screen hero on mobile
.hero {
height: 100dvh;
display: flex;
align-items: center;
justify-content: center;
}Full-screen with fallback for older browsers
.hero {
height: 100vh; /* fallback */
height: 100dvh; /* modern browsers override */
}Browsers that do not understand dvh ignore the second declaration and use 100vh.
Sticky footer layout
.page-wrapper {
min-height: 100svh;
display: flex;
flex-direction: column;
}
.main-content {
flex: 1;
}Using svh here ensures the footer stays at the bottom even when the toolbar is fully expanded.
Mobile modal overlay
.modal-overlay {
position: fixed;
inset: 0;
height: 100dvh;
background: rgba(0, 0, 0, 0.5);
}A fixed modal should always cover the exact visible area. dvh handles toolbar transitions seamlessly.
Common Mistakes and How to Fix Them
Mixing vh and dvh in the same layout. If a parent uses 100vh and a child uses 100dvh, they reference different viewport sizes. The child may overflow or underflow the parent. Pick one unit family and stay consistent within a component.
Using dvh on scroll-heavy animated elements. A parallax section sized with dvh recalculates on every toolbar transition. This triggers layout reflow and can drop frames below 60fps. Use svh or lvh for these cases instead.
Forgetting the fallback. About 4% of global users still run browsers that do not support the new units (mostly older Android WebView and some embedded browsers). Always declare vh before dvh:
.element {
height: 100vh;
height: 100dvh;
}Using dvh with position: fixed on iOS Safari. In some iOS versions, fixed-position elements with dvh can cause visual jitter during toolbar transitions. If you notice flickering, switch to svh and accept the slight gap when the toolbar retracts.
Virtual Keyboards and Viewport Units
When a virtual keyboard opens on mobile, it does not change viewport unit values by default. Your 100dvh element stays the same height even though the keyboard covers half the screen.
Chrome offers an opt-in via the interactive-widget meta tag:
<meta name="viewport" content="width=device-width, initial-scale=1, interactive-widget=resizes-content">With resizes-content, viewport units shrink to account for the keyboard. This is useful for chat interfaces or forms where you want the input area to remain visible. The default value overlays-content keeps the old behavior.
Browser Support
All modern browsers support dvh, svh, and lvh as of early 2023:
| Browser | Version | Release Date |
|---|---|---|
| Chrome | 108+ | December 2022 |
| Firefox | 101+ | May 2022 |
| Safari | 15.4+ | March 2022 |
| Edge | 108+ | December 2022 |
| Samsung Internet | 20+ | 2023 |
Global support is above 96% according to Can I Use data. The main gaps are older Android WebView instances and some niche browsers.
Using dvh with Tailwind CSS
Tailwind's built-in h-screen class still maps to 100vh. To use the new viewport units, you have two options:
Arbitrary values (no config needed):
<div class="h-[100dvh]">Full dynamic viewport height</div>
<div class="h-[100svh]">Full small viewport height</div>Custom utility in your CSS (Tailwind v4):
@theme {
--height-dvh: 100dvh;
--height-svh: 100svh;
}Then use h-dvh and h-svh directly in your markup. If you are working with custom values in Tailwind CSS v4, this follows the same pattern.
If you are building dark mode with CSS, these viewport units combine well with modern CSS features to create truly adaptive interfaces.
For production use in 2026, the safe default is dvh with a vh fallback. Reserve svh for initial-load-critical layouts and lvh when you explicitly want the expanded viewport size.
Comments (0)
Sign in to comment
Report