Using vw, vh, dvh, svh, and lvh Viewport Units for Responsive Sizing
Understand dvh, svh, and lvh viewport units, how they handle mobile browser chrome, and when to use container query units instead.
Your full-screen hero is cut off at the bottom on a phone every time the address bar is visible, and you have been fighting it with 100vh. The fix is not a hack. CSS viewport units dvh, svh and lvh exist exactly for this, and each measures a different viewport state. vh measures the largest possible viewport height, the one you get when browser chrome is fully hidden. On mobile it overflows when the address bar is showing. dvh tracks the dynamic viewport as the chrome appears and disappears. svh locks to the smallest visible height. lvh matches the old vh behaviour. Use dvh for anything that should fill the visible screen, svh for elements that must never be clipped even with the chrome out, and lvh only when you need the pre-2022 behaviour on purpose.
dvh vs vh CSS viewport unit
How vh Breaks On Mobile
The difference between dvh and vh is not a rounding error or a vendor preference. vh is a fixed unit: 1vh equals 1% of the layout viewport’s height when the browser chrome is fully retracted, the tallest state the viewport can reach. On a desktop browser that is the window height, and vh behaves as you expect. On a mobile browser, the layout viewport changes size as the address bar slides up and down, but vh does not follow it. It keeps resolving against the largest possible height. An element set to 100vh is taller than the visible area whenever the address bar is present, and the bottom of the element is pushed below the fold, requiring a scroll or leaving a gap.
The dvh Fix And Fallback
/* Fallback first, then dynamic override */
.hero {
height: 100vh;
height: 100dvh;
}
dvh, the dynamic viewport height unit, resolves against the viewport’s current height, shrinking and growing as the browser chrome shows or hides. When the user scrolls down and the address bar collapses, 100dvh matches the full visible height; when they scroll up and the bar reappears, 100dvh shrinks to fit. This is the unit for a hero section, a full-screen modal backdrop, or any element that must always exactly fill what the user can see. The fallback pattern above is the accepted one: write vh first for older browsers, then dvh for the ones that support it. The degradation for a browser that only reads the first declaration is a partially obscured element on some mobile browsers, not a broken layout, because vh gives you the full height and the page scrolls.
svh lvh viewport unit difference
The Two Stable Extremes
svh and lvh are the two stable extremes of the same dynamic range that dvh tracks. svh, the small viewport height unit, equals 1% of the smallest possible viewport height: the state where the browser chrome is fully expanded, including the address bar at the top and the navigation bar at the bottom on some devices. lvh, the large viewport height unit, equals 1% of the largest possible viewport height, which is the same value vh uses. The practical difference is that svh never changes on a page, and lvh never changes either, regardless of whether the user scrolls or the chrome moves. dvh changes, because it follows the actual visible area.
/* svh for a sticky footer that must never be covered */
.footer-sticky {
position: fixed;
bottom: 0;
height: 100svh; /* always fits, no chrome overlap */
}
/* lvh for a full-height background that can be slightly tall */
.background-layer {
height: 100lvh; /* matches vh, never shrinks */
}
When To Choose svh Over dvh
When the element must not be clipped under any condition, even if that means it does not quite fill the screen when the chrome is hidden. A fixed navigation bar at the bottom, a call-to-action button that must remain tappable, a login form that cannot be pushed off-screen: all cases where svh is the safe choice. lvh is rarely needed now that dvh exists, but it serves one purpose. If you have a layout that depends on the viewport never changing size, such as a canvas-based game or a full-screen iframe, lvh gives you that stability with the conventional vh value, while signalling to a future maintainer that the size is intentional.
Viewport Unit Mobile Browser Chrome
Why Mobile Chrome Breaks vh
Mobile browser chrome is the collective name for the user interface surrounding the web content: the address bar, the tab bar, the bottom navigation buttons, and the virtual keyboard. Each of these takes up space that is part of the layout viewport, and when they appear or disappear, the visible area changes. vw and vh units were defined before mobile browsers had dynamic chrome, so they measure the largest viewport, not the current one. That is the root of the failure you see in the wild: a developer writes 100vh, assumes it fills the screen, and on a phone with the address bar visible, the element is taller than the screen by exactly the height of that bar.
The visual viewport is the portion of the page the user currently sees, and it can be smaller than the layout viewport, especially when the user zooms. The dynamic viewport units (dvh, dvw) track the layout viewport’s current size, the one that matters for layout. The small and large variants (svh, svw, lvh, lvw) give you the two endpoints. You cannot use dvh to measure the visual viewport itself, and you should not try: the visual viewport is a separate concept, relevant to pinch-zooming and accessibility, and layout units are not the right tool for it.
The Defensive Production Pattern
@supports (height: 100dvh) {
.app-shell {
height: 100dvh;
}
}
@supports not (height: 100dvh) {
.app-shell {
height: 100vh;
}
}
The @supports query above is the defensive pattern for production code. It checks for dvh support before applying it, and falls back to vh when dvh is missing. The fallback is not a perfect match: on a mobile browser that does not support dvh, the element will be taller than the visible area when the chrome is expanded, and the user may have to scroll. That is the accepted cost, because every browser that supports dvh also supports @supports, and the set of browsers that support vh but not dvh is small and shrinking. Check the caniuse data for “dvh” rather than guessing from a blog post.
vw vh Responsive Sizing CSS
vw and vh are the original viewport-percentage lengths, defined in CSS Values and Units Module Level 3. vw is 1% of the viewport width, vh is 1% of the viewport height, and they were widely available since July 2015, per the MDN Baseline status. They solved a real problem: making an element size relative to the browser window rather than its parent. Before them, full-viewport sections required JavaScript to read the window dimensions, and responsive typography required media queries at every breakpoint. vw and vh made it declarative.
/* Basic vw/vh usage */
.section {
width: 100vw; /* full viewport width */
height: 50vh; /* half viewport height */
}
/* vmin/vmax for safe squares */
.square {
width: 90vmin; /* 90% of the smaller dimension */
height: 90vmin;
}
Use vw and vh for elements that genuinely relate to the viewport edge: a full-width header, a full-height landing section on desktop, a minimum height for a main content area. Do not use them for component sizing inside a layout. A card that should be half the width of its parent should use 50%, not 50vw, because vw ignores the parent entirely and will overflow when the parent is narrower than the viewport. The same rule applies to vh: if the element lives inside a parent with a defined height, a percentage is the correct unit, and vh will break when the parent is shorter or taller than expected. This is the most common mistake in responsive sizing, and it is why container query units exist.
Container Query Units cqw cqh
Container query units are the size-based counterparts to viewport units, but they resolve against a query container instead of the viewport. cqw is 1% of the container’s inline size (width in horizontal writing modes), cqh is 1% of the container’s block size (height), and cqmin and cqmax are the smaller and larger of the two. They were designed for exactly the case where vw and vh fail: a component that should fill or size relative to its container, not the viewport.
.card-container {
container-type: inline-size;
}
.card {
width: 100cqw; /* full container width */
height: 50cqh; /* half container height, if block-size containment */
}
To use cqh for height, the container must have container-type: size or container-type: inline-size, and you must be aware of a constraint: a container with container-type: size cannot have a height that depends on its contents, because that would create a circular dependency. In practice, you pair container-type: inline-size with an explicit height on the container, or you accept that cqh resolves against the container’s computed block size. The rule of thumb: if the element lives inside a sized container, use container query units; if it genuinely relates to the viewport edge, use viewport units. A card inside a grid track should use cqw, not vw. A full-screen modal that should cover the visible area should use dvh, not cqh, unless the modal’s parent is already constrained to the viewport.
Container query units have a real interop gap: the spec for size container queries shipped with support in Chrome 105, Safari 16, and Firefox 110, but older versions of Safari that supported container queries had bugs in cqw and cqh resolution. Check the caniuse data for “container query units” before relying on them in production, and provide a fallback using percentage or vw when the target browser is old.
When vh is the Wrong Unit and dvh is the Fix
There is a specific failure case that should send you straight to dvh: a full-screen element that is clipped at the bottom on a mobile browser. The symptom is that the element’s bottom edge is hidden behind the browser’s bottom bar, or that the page scrolls when you did not want it to, revealing empty space below the element. The cause is vh measuring the large viewport height, and the fix is a single declaration.
/* Before: clipped on mobile */
.hero {
min-height: 100vh;
}
/* After: always fills the visible area */
.hero {
min-height: 100vh;
min-height: 100dvh;
}
The reason the fallback comes first is that older browsers ignore the second declaration, so they get vh, which is slightly too tall but not broken: the page scrolls. Newer browsers apply dvh, which tracks the current viewport, so the hero fits exactly. This is a progressive enhancement, not a polyfill. It costs one extra line and zero performance overhead, because dvh is a CSS length like vh, computed at layout time with no JavaScript.
One warning: do not use dvh for every height. A sidebar that should be as tall as its content, a modal that should size to its own content, a button with a fixed height: all should use other units. dvh is for elements that should match the visible viewport height, not for every tall element. If you use it indiscriminately, you will create layouts that reflow as the address bar appears and disappears, which is janky for the user. The dynamic part of dvh is a feature only when you want the element to change size with the chrome.
The Vocabulary of Viewport Sizing, from vw to cqmax
Viewport States And Their Names
Knowing the units is half the battle; the other half is the vocabulary that lets you debug and discuss them. The layout viewport is the rectangle that contains the page, and it is the containing block for viewport-percentage lengths. The visual viewport is what the user sees, which can be smaller when zoomed. The initial containing block is the viewport-sized rectangle at the root of the document. Browser chrome is the UI that surrounds the page. The virtual keyboard is a special case of chrome that appears on mobile and can shrink the layout viewport significantly, which is why svh exists: to give you a size that works even when the keyboard is up.
These terms matter because they are the difference between a fix that works and one that does not. If you say “the viewport is smaller”, you are being vague; if you say “the layout viewport height is reduced by the address bar”, you know to reach for dvh. The units themselves are named after these states: sv for small, lv for large, dv for dynamic. The same pattern applies to width units (svw, lvw, dvw) and to the logical units vi and vb, where vi is 1% of the viewport’s inline size and vb is 1% of its block size, useful for vertical writing modes.
Container Units And The Reference Frame Rule
Container query units follow a parallel naming scheme: cqw and cqh for width and height, cqmin and cqmax for the smaller and larger. The “q” stands for query, and the unit resolves against the nearest ancestor with a container-type declaration. The distinction between viewport units and container units is not about which is newer; it is about which reference frame the element should use. If the element’s size should depend on the window, use viewport units. If it should depend on its parent, use container units or percentages. Mixing them is a source of bugs, because the two reference frames can diverge wildly on a mobile device.
FAQ: Viewport Units and Browser Support
Why does my 100vh element overflow on mobile?
Because vh measures the largest possible viewport height, which you only get when the browser chrome is hidden. On a phone with the address bar visible, the visible area is smaller, so 100vh is taller than the screen. Use 100dvh instead, which tracks the current viewport height.
Is dvh safe to use in production?
Yes, for most sites. dvh is supported in Chrome 108+, Firefox 101+, and Safari 15.4+. Check the caniuse data for a current coverage breakdown; the figure shifts monthly. Use the vh fallback first for older browsers, and the degradation is a slightly too-tall element that scrolls, not a broken layout.
What is the difference between svh and lvh?
svh is the small viewport height, the smallest the visible area can be, when the address bar and bottom bar are expanded. lvh is the large viewport height, the largest visible area, equivalent to vh. svh never changes, lvh never changes, and dvh changes between them.
When should I use container query units instead of viewport units?
Use cqw and cqh when the element lives inside a sized container and should size relative to that container, not the viewport. A card inside a grid track should use cqw for width. Use viewport units only when the element genuinely relates to the viewport edge, like a full-screen hero.
Table: Viewport and Container Unit Reference
| Unit | Reference frame | 1% equals | Typical use | Best for |
|---|---|---|---|---|
| vw | Layout viewport width | 1% of viewport width | Full-width elements, fluid typography | Elements that span the window width |
| vh | Layout viewport height (large) | 1% of largest viewport height | Full-height sections, sticky headers | Desktop layouts where the window is fixed |
| dvh | Dynamic viewport height | 1% of current visible height | Full-screen mobile heroes, modals | Elements that must fill the visible area |
| svh | Small viewport height | 1% of smallest visible height | Bottom bars, fixed footers | Elements that must never be clipped |
| lvh | Large viewport height | 1% of largest visible height | Stable full-height backgrounds | Layouts that must not reflow on scroll |
| cqw | Container inline size | 1% of container’s width | Cards, grid items | Components inside a sized parent |
| cqh | Container block size | 1% of container’s height | Vertical components | Elements sized by a container’s height |
| cqmin | Container smaller dimension | 1% of min(width, height) | Square avatars, badges | Elements that must fit both axes |
| cqmax | Container larger dimension | 1% of max(width, height) | Cover-like backgrounds | Elements that should fill the larger axis |
The Cost of Getting It Wrong, and the One Rule That Fixes It
The Performance Cost Of dvh
Every viewport unit you choose has a layout, paint, and composite cost, and the cost is not the unit itself but the reflow it triggers. When you use dvh, the browser must recompute the element’s height every time the viewport changes, which happens on every scroll when the address bar animates. On a low-end mobile device, that recomputation can drop the frame rate from 60fps to 10fps if you have dozens of dvh elements, because each one triggers a layout pass. The rule: use dvh for one or two full-screen elements per page, not a hundred. The dynamic nature is the point, but it is also the cost.
For everything else, prefer the stable units. vh and lvh never change, so the browser computes the height once and does not re-layout on scroll. svh is also stable, and it is the right choice for anything that must stay visible, because it sacrifices dynamism for safety. The performance-conscious developer reads this as: use vh for desktop-like layouts, dvh for the one or two mobile-critical full-screen elements, and svh for fixed UI that must never be covered. That split gives you the correct visual result and keeps the layout pass count low.
The Component Portability Rule
If you are building a design system, the rule is even simpler: never let a component set its own viewport height unless the component is designed to be a full-screen overlay. For a card, a button, or a form field, the height should come from content, padding, or a container query unit, not from the viewport. The viewport is the wrong reference frame for a component that might live in a sidebar, a grid cell, or a modal. Container query units exist for that case, and using them is what makes a component portable. A component that sizes against the viewport will break the moment you put it in a different context, and the developer who inherits it will spend an hour debugging why 100vh is wrong inside a 50vh container.
Who Should Use Viewport Units and Who Should Not
Viewport units, especially dvh and svh, are the right tool for a front-end developer building a marketing site, a landing page, or a web app with a full-screen onboarding flow. The units solve a real mobile problem without JavaScript, and the fallback pattern is simple enough to explain in a code review. If your site has a hero section, a fixed header, or a bottom sheet, dvh and svh are the answer. Ship them today.
Viewport units are not for everyone. If you are building a complex dashboard with many nested panels, a component library where elements must work in arbitrary containers, or a text-heavy page where height is driven by content, viewport units will fight you. For those cases, use container query units, percentages, or intrinsic sizing. Avoid overusing dvh on low-end devices, because the reflow cost is real. And if you are a technical writer or educator, use viewport units to teach the difference between the layout viewport and the visual viewport, but do not present vh as the default for every height; the default should be content-driven, with viewport units as the exception.
If your layout genuinely relates to the viewport edge, viewport units are the correct choice. If it does not, they are the wrong choice, no matter how easy they are to type. That is the line, and it is the difference between a page that works on every device and a page that breaks on the one device your users actually carry.