CSS svh, lvh and dvh Explained: Why 100vh Breaks on Mobile and What to Use Instead
In short: On mobile, 100vh is measured as if the browser toolbar were hidden, so full-screen layouts get cut off while the address bar is showing. Use 100svh when content must never overflow, 100dvh when it should match the visible screen at every moment, and keep a 100vh line above as a fallback. Support is wide: caniuse shows Chrome and Edge 108, Firefox 101, Safari and iOS Safari 15.4 and Samsung Internet 21 as the first versions, about 96% of global usage (checked 2026-10-12).
svh, lvh, dvh, svw, lvw, dvw in one line each
Per MDN, each viewport unit comes in three variants:
- svh (small viewport height): 1% of the height when the browser UI (address bar, toolbars) is fully expanded, so the viewport is at its smallest.
- lvh (large viewport height): 1% of the height when the browser UI is retracted, so the viewport is at its largest.
- dvh (dynamic viewport height): 1% of the height you can actually see right now. It updates as the address bar collapses and expands while the user scrolls.
- svw / lvw / dvw: the same three variants for width instead of height.
The width variants are not directly tied to the mobile address bar, so you will notice them less. They are defined as the same small/large/dynamic set and matter in a few special layouts such as horizontal carousels or rotation-sensitive designs.
Why 100vh breaks on mobile
The classic vh unit had a single definition: 1% of the viewport height. On desktop that works because the window does not change size as you scroll.
Mobile browsers, especially iOS Safari and Chrome on Android, hide the address bar and bottom toolbar as you scroll down and bring them back when you scroll up. The visible area changes, and for years browsers disagreed about whether vh should be calculated with the toolbar showing or hidden. Today, plain vh is equivalent to lvh, the large viewport.
The typical symptom: a full-screen hero section set to height: 100vh is cut off at the bottom when the page first loads (the address bar is showing, but 100vh assumes it is hidden), or a fixed bottom navigation bar is pushed below the visible screen.
The CSS Values and Units Module Level 4 specification solved this by naming three viewport states, which is where the small, large and dynamic units come from.
Understanding small, large and dynamic viewports
Picture a mobile browser:
- Page just opened: the address bar and bottom toolbar are both visible. The vertical space available to your content is the small viewport.
- Scrolled down until the toolbars are gone: content gets the whole screen. That is the large viewport.
- Right now, with the toolbar half collapsed or fully expanded: the dynamic viewport, a value that changes live.
100svh is always calculated with the toolbar showing, so it never overflows. The trade-off is empty space at the bottom once the toolbar disappears. 100lvh uses the toolbar-hidden size, so when the page first loads the bottom of your content can sit off screen. 100dvh moves between the two and is recalculated whenever the toolbar state changes.
Which unit should you use?
dvh: when you want to fill the visible screen exactly
For a full-screen hero or the first screen of an app-like landing page, dvh is the most intuitive choice.
.hero {
height: 100dvh;
display: flex;
align-items: center;
justify-content: center;
}
Because dvh is recalculated during scrolling, MDN notes that its values are not stable and can cause resizing while scrolling, with some performance cost. For elements where layout shifts would be distracting, such as an animated background, consider svh instead.
svh: when it must never overflow
For a CTA bar fixed to the bottom of the screen or a modal overlay, svh is the safe choice because it assumes the smallest viewport.
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
/* Calculated against the smallest viewport (toolbar showing),
so it always stays on screen */
min-height: 10svh;
}
lvh: when you want the fully expanded size
It is used less often. For example, you might want a background image to fill the screen as it looks once the toolbar is gone. Just remember the bottom of the content may be hidden on first load.
Modal example: dvh follows the screen live
.modal {
position: fixed;
inset: 0;
height: 100dvh;
overflow-y: auto;
padding: 1rem;
}
A modal with its own scroll area opens to fit the current screen, whatever state the browser UI is in, which avoids awkward scroll positions.
When to use svw and dvw
Horizontal units are mostly unrelated to the mobile address bar, but they matter in cases like these:
- Sizing cards by viewport width in a horizontally scrolling gallery or carousel
- Layouts that must react immediately when the device rotates
- PWAs and full-screen apps, where browser chrome disappears and can affect width calculations
.carousel-card {
width: 80dvw;
max-width: 320px;
}
For ordinary responsive layouts, vw, % and watching out for the small horizontal scrollbar bug caused by 100vw (which includes the scrollbar width) are usually enough. You only need svw or dvw when a particular mobile browser's UI affects width.
Browser support and fallbacks
According to caniuse, the small, large and dynamic viewport units (including svmin, dvmin and the other derived units) first shipped in these versions:
| Browser | First supported version |
|---|---|
| Chrome | 108 |
| Edge | 108 |
| Firefox | 101 |
| Safari (desktop) and iOS Safari | 15.4 |
| Samsung Internet | 21 |
If you must support older enterprise browsers or embedded web views, add a fallback. CSS ignores values it does not understand and applies the last valid declaration, so two lines in this order work:
.hero {
height: 100vh; /* fallback for older browsers */
height: 100dvh; /* overrides in supporting browsers */
}
You can also be explicit with @supports:
@supports (height: 100dvh) {
.hero {
height: 100dvh;
}
}
When to replace vh in existing code
You do not have to rewrite every 100vh. Change only the elements that actually misbehave when the mobile address bar collapses and expands.
- Full-screen hero or the first screen of a landing page: switch to
100dvh. - Fixed bottom bars or CTAs that must always be visible: use an
svh-based minimum height so they cannot overflow. - Ordinary cards and section heights: usually no change needed.
vh,%orautooften work fine. - A loose "fill at least the screen" rule such as
min-height: 100vh: either100lvhor100dvhis acceptable.
svmin, svmax, dvmin and dvmax
The specification also defines svmin, lvmin, dvmin, svmax, lvmax and dvmax. Just as vmin uses the smaller of width and height and vmax the larger, these follow the same rule inside each of the three viewport states. dvmin can be handy for near-square elements that scale with the screen and should stay stable through rotation. They are used less often in practice than svh and dvh.
Real bug: a 100vh modal cut off on iOS
A common report: a full-screen modal with height: 100vh has its bottom button pushed off screen in iOS Safari. The cause is the one described above: 100vh assumes the toolbar is hidden, but when the modal opens the address bar is still showing, so the content is taller than the visible screen. This usually fixes it:
.modal-fullscreen {
height: 100vh; /* fallback */
height: 100svh; /* safe value that never overflows */
height: 100dvh; /* live value in supporting browsers */
overflow-y: auto;
}
Putting 100svh in the middle means a browser that understands svh but not dvh still gets a value that stays on screen. Declaring the three lines in order sorts out the fallback priority.
When you need JavaScript
For complex layouts where CSS units are not enough, such as when you need an exact pixel value in JavaScript, you can combine them with the window.visualViewport API:
const vh = window.visualViewport
? window.visualViewport.height
: window.innerHeight;
document.documentElement.style.setProperty('--real-vh', `${vh}px`);
Storing the real viewport height in a CSS variable and using height: var(--real-vh) was the common workaround before dvh existed. Today CSS alone solves most cases. JavaScript is still useful for fine-grained interactions tied to resize or scroll events, for example changing the layout when the on-screen keyboard opens.
Tailwind CSS and CSS-in-JS
Tailwind CSS includes utilities such as h-dvh, h-svh and h-lvh (see the Tailwind height docs), along with size-dvh and similar classes. Changing a hero from h-screen (which uses 100vh) to h-dvh is often enough to fix the mobile behavior. On an older Tailwind version that lacks these, add custom values under theme.extend:
// tailwind.config.js example (for older versions)
module.exports = {
theme: {
extend: {
height: {
dvh: '100dvh',
svh: '100svh',
},
},
},
};
In CSS-in-JS libraries such as styled-components or Emotion, you can pass 100dvh as a plain string. If a browser does not understand it, only that declaration is ignored, so the same rule applies: put the fallback first and the new unit on the next line.
Common questions
Is dvh always better than svh? No. If you do not want the layout to shift during scrolling, svh or a fixed value is better. dvh is for when you need the value to be exactly right in real time, not a strict upgrade.
What is the difference between 100% and 100dvh? height: 100% is relative to the parent's height, so it does nothing if the parent has no explicit height. dvh, svh and lvh always refer to the browser viewport regardless of the parent, so they work in nested layouts.
Should I put 100dvh on the body? For top-level elements like body or html, min-height is usually safer. A fixed height can break scrolling when the content is taller than the viewport, so min-height: 100dvh fills the screen and still grows with long content.
Summary
vh was a single value designed around desktop. svh, lvh and dvh, and their width versions svw, lvw and dvw, split the viewport into three states to reflect mobile toolbars that appear and disappear. Use svh for anything that must never overflow, and dvh for anything that should match the visible screen in real time. With support in all major browsers since 2022, if 100vh has been causing trouble on mobile, it is a safe fix to apply now.