
New Viewport Units: dvh, svh, and lvh Explained
If you've ever built a full-screen hero section with height: 100vh, you've probably opened it on a phone and found the bottom cut off. The call-to-action button that was perfectly placed on your laptop is now hidden behind the browser's toolbar, and users have to scroll just to see it. For years the fix involved JavaScript that measured window.innerHeight and wrote it into a custom property on every resize.
CSS now has a proper solution: three sets of viewport units, small (svh), large (lvh), and dynamic (dvh), that describe the viewport in its different states. They've been supported in all major browsers for a while now, and they're one of the easiest wins you can add to a mobile layout. This guide explains why the original vh behaves the way it does, what each new unit measures, which one to choose in common situations, and a few edge cases, like the on-screen keyboard, that still need attention.
Why 100vh Breaks on Mobile
On desktop, the viewport is simple: it's the area of the browser window that shows the page, and its size only changes when the user resizes the window.
Mobile browsers are different. To give content more room, they collapse their user interface as you scroll. The address bar shrinks or slides away, and sometimes a bottom toolbar disappears too. That means the visible area of the page changes size during normal scrolling:
- When the page first loads, the browser UI is expanded, and the visible area is at its smallest.
- After the user scrolls down, the UI retracts, and the visible area is at its largest.
When mobile browsers first had to decide what vh should mean, they faced a problem. If 1vh tracked the visible area in real time, every element sized in vh would resize while the user scrolled, causing constant layout shifts and jank. So browsers settled on a fixed definition: 100vh equals the largest possible viewport, the height with the UI fully retracted.
That's why a 100vh element is taller than the screen when the page first loads. The difference is exactly the height of the expanded browser UI, and that's the part that gets hidden.
The Three New Viewport Sizes
The CSS Values and Units specification formalized the idea that there are several viewports, and gave each one its own units.
Small viewport: svh
The small viewport is the visible area when the browser UI is fully expanded. It's the most conservative measurement: an element that's 100svh tall always fits on screen, whether the toolbars are showing or not.
.hero {
min-height: 100svh;
}
Large viewport: lvh
The large viewport is the visible area when the browser UI is fully retracted. It's the maximum possible height. In practice, lvh matches how vh has always behaved on mobile.
.background-video {
height: 100lvh;
}
Dynamic viewport: dvh
The dynamic viewport is whatever the visible area is right now. When the UI is expanded, 100dvh equals 100svh. When it retracts, 100dvh grows to match 100lvh. It updates as the browser UI animates in and out.
.app-shell {
height: 100dvh;
}
What about desktop?
On desktop browsers, where the UI doesn't collapse on scroll, all three are the same size, and they all equal vh. The new units only behave differently on devices with dynamic toolbars, which in practice means mobile and some tablet browsers.
The Full Family of Units
Height is the most common use, but each viewport type has a complete set of units:
| Classic | Small | Large | Dynamic | Meaning |
|---|---|---|---|---|
vh | svh | lvh | dvh | 1% of viewport height |
vw | svw | lvw | dvw | 1% of viewport width |
vi | svi | lvi | dvi | 1% of viewport inline size |
vb | svb | lvb | dvb | 1% of viewport block size |
vmin | svmin | lvmin | dvmin | 1% of the smaller dimension |
vmax | svmax | lvmax | dvmax | 1% of the larger dimension |
The width variants rarely differ from one another today, since browser UI mostly collapses vertically, but they exist for consistency and for future devices where that might change. The logical vi and vb versions adapt to vertical writing modes.
Choosing the Right Unit
Here's the practical decision guide.
Use svh for most full-screen layouts
For a hero section, a landing screen, or a splash panel where important content must be visible on load, svh is the safest choice. The content fits on screen immediately, and nothing resizes during scrolling.
.hero {
display: grid;
place-content: center;
gap: 1.5rem;
min-height: 100svh;
padding: 2rem;
text-align: center;
}
When the user scrolls and the toolbar retracts, the hero will be slightly shorter than the visible area, revealing a bit of the next section. That's usually fine, and it's often a helpful cue that there's more below.
Using min-height instead of height is deliberate. If the hero's content is taller than the viewport, such as on a short landscape phone, min-height lets it grow instead of overflowing.
Use dvh for app-like layouts that must fill the screen exactly
For interfaces that behave like apps, such as a chat window with a pinned input, a full-screen map, or a modal dialog with a fixed footer, you often want the layout to match the visible area at all times.
<div class="chat">
<header class="chat__header">Support chat</header>
<main class="chat__messages">...</main>
<form class="chat__composer">
<input type="text" aria-label="Message" />
<button type="submit">Send</button>
</form>
</div>
.chat {
display: grid;
grid-template-rows: auto 1fr auto;
height: 100dvh;
}
.chat__messages {
overflow-y: auto;
overscroll-behavior: contain;
}
With 100dvh, the composer stays pinned just above the browser's bottom toolbar, and the message list takes whatever space remains. In this kind of layout the page itself usually doesn't scroll; only the messages panel does, so the browser UI rarely collapses and resizing isn't a concern.
Use lvh for decorative backgrounds
For a fixed background image or video that should always cover the screen, even when the toolbar retracts, lvh guarantees there's never a gap:
.page-backdrop {
position: fixed;
inset: 0 0 auto;
height: 100lvh;
background: url("/images/texture.jpg") center / cover;
z-index: -1;
}
Parts of it may be hidden under the expanded toolbar, but since it's decorative, that doesn't matter.
Be cautious with dvh on scrolling pages
It's tempting to replace every vh with dvh, but on a normal scrolling page that can backfire. When the browser UI retracts mid-scroll, every dvh-sized element resizes, and everything below it shifts. On a long page with several 100dvh sections, that can look like the content is jumping under the user's finger. It also means extra layout work during scrolling.
A good rule: dvh for fixed, app-like shells where the page doesn't scroll; svh for sections within a scrolling page.
Providing Fallbacks
All current major browsers support the new units, but if you support older versions, include a classic fallback first. Browsers ignore declarations with units they don't understand and keep the previous one:
.hero {
min-height: 100vh;
min-height: 100svh;
}
If you'd rather group the new-unit rules together, a feature query works too:
.app-shell {
height: 100vh;
}
@supports (height: 100dvh) {
.app-shell {
height: 100dvh;
}
}
Retiring the JavaScript workaround
You may have code like this in an older project:
function setViewportHeight() {
document.documentElement.style.setProperty(
"--vh",
`${window.innerHeight * 0.01}px`,
);
}
window.addEventListener("resize", setViewportHeight);
setViewportHeight();
.hero {
height: calc(var(--vh, 1vh) * 100);
}
It worked, but it ran JavaScript on every resize event and could flash the wrong size before the script ran. You can safely replace it with 100dvh (to match its behavior) or, better for most sections, 100svh. The same goes for the old -webkit-fill-available trick for iOS Safari, which was nonstandard and inconsistent across browsers.
The On-Screen Keyboard
Here's an edge case the new units don't fully solve. When a user taps an input and the virtual keyboard slides up, what happens to the viewport?
By default, most mobile browsers don't resize the layout viewport for the keyboard. Instead, the keyboard overlays the page, and the browser scrolls to keep the focused input visible. That means dvh usually does not shrink when the keyboard opens, and an element pinned to the bottom with 100dvh may end up behind the keyboard.
Chromium-based browsers let you change this behavior with the interactive-widget key in the viewport meta tag:
<meta
name="viewport"
content="width=device-width, initial-scale=1, interactive-widget=resizes-content"
/>
With resizes-content, the layout viewport shrinks when the keyboard opens, and viewport units, including dvh, shrink with it. Other values are resizes-visual (the default in Chromium) and overlays-content. Support outside Chromium is limited, and Safari doesn't support it at the time of writing, so check caniuse and test on real devices.
For precise control, the VisualViewport JavaScript API reports the area that's actually visible, including the keyboard's effect:
const vv = window.visualViewport;
function updateKeyboardInset() {
const inset = window.innerHeight - vv.height - vv.offsetTop;
document.documentElement.style.setProperty(
"--keyboard-inset",
`${Math.max(0, inset)}px`,
);
}
vv.addEventListener("resize", updateKeyboardInset);
vv.addEventListener("scroll", updateKeyboardInset);
.chat__composer {
margin-bottom: var(--keyboard-inset, 0px);
}
Use this sparingly; for many forms, the browser's default behavior of scrolling the input into view is good enough.
Safe Areas and Notches
Viewport units measure the viewport, but on phones with notches, rounded corners, and home indicators, some of that viewport is covered by hardware or system UI. To let your layout extend edge to edge while keeping content clear of those areas, combine the viewport units with env() safe-area insets:
<meta
name="viewport"
content="width=device-width, initial-scale=1, viewport-fit=cover"
/>
.bottom-bar {
position: fixed;
inset-inline: 0;
bottom: 0;
padding: 0.75rem 1rem;
padding-bottom: calc(0.75rem + env(safe-area-inset-bottom, 0px));
}
viewport-fit=cover tells the browser your page will handle the unsafe areas itself, and env(safe-area-inset-bottom) returns the height of the home indicator zone. The second argument is a fallback for devices without one.
Common Pitfalls
- Using
heightinstead ofmin-height: A fixed100svhheight can clip content on small or landscape screens. Prefermin-heightfor content sections. - Replacing every
vhwithdvh: On scrolling pages, it causes content to shift as the toolbar collapses. Usesvhfor scrolling pages. - Expecting
dvhto handle the keyboard : By default it doesn't. Useinteractive-widgetwhere supported, or theVisualViewportAPI. - Forgetting the fallback : Very old browsers drop the declaration entirely, so always put a
vhvalue first if you support them. - Using
100vwfor width : The width units still include the scrollbar on desktop platforms with classic scrollbars, which can cause horizontal overflow.width: 100%is usually what you want for full-width blocks. - Testing only in desktop device emulation : DevTools' mobile mode doesn't simulate a collapsing toolbar. Test on a real phone, or at least in a mobile simulator, to see the difference between the units.
Browser Support
The small, large, and dynamic viewport units are supported in current versions of Chrome, Edge, Firefox, and Safari, on both desktop and mobile. The interactive-widget viewport setting is primarily a Chromium feature. Safe-area env() variables are widely supported. Because the fallback pattern is a single extra line, there's little reason not to adopt the new units today.
Conclusion
The new viewport units give names to something mobile browsers have been doing for years: the viewport isn't one size, it's a range. svh is the smallest, always-visible size and the best default for full-screen sections. lvh is the largest size, matching classic vh and useful for backgrounds. dvh follows the visible area in real time, making it ideal for app-like shells that must fit the screen exactly.
Replace 100vh heroes with min-height: 100svh, use 100dvh for fixed layouts like chat and maps, keep a vh fallback for older browsers, and handle the on-screen keyboard separately when it matters. With those habits, the cut-off mobile hero becomes a thing of the past.


