In CSS, why does a `height: 100vh` panel get clipped on mobile browsers, and how do the svh, lvh, and dvh units address it?
answer
- the visible area is not constant on mobile
- stability was chosen over live resizing
- small, large, dynamic viewport
- clipping versus resizing during scroll
- shipped across engines during 2022
basics
~20 sMobile browsers size vh against the large viewport, with retractable toolbars assumed hidden, so 100vh exceeds the visible area while the toolbars are shown. svh is the small (toolbars shown) viewport, lvh the large one, and dvh tracks the current value as toolbars retract.
solid answer
~50 sOn mobile, the visible viewport changes size as the URL bar and toolbars retract during scrolling. Rather than reflow the page constantly, browsers keep `vh` stable by pinning it to the **large** viewport — the size with those toolbars retracted. So while the toolbars are visible, `100vh` is taller than what you can see, and the bottom of a full-screen panel is cut off, which is fatal for a hero with a call-to-action at the bottom. The viewport units shipped across browsers in 2022 give you the choice explicitly: `svh` is the **small** viewport (toolbars shown), `lvh` the **large** viewport (the same value `vh` has always used), and `dvh` is **dynamic** — it re-resolves as the toolbars move. Practical use: `100svh` when nothing may ever be clipped, `100dvh` when you want the panel to track the real visible area and can tolerate it resizing mid-scroll. The same three prefixes exist for the other axes and shorthands: `svw/lvw/dvw`, `svmin`, `dvmax`, and so on.
code
css · 10 lines.hero {
height: 100vh; /* fallback for older engines */
height: 100svh; /* never clipped by mobile toolbars */
}
.overlay {
position: fixed;
inset: 0;
height: 100dvh; /* tracks the visible area as toolbars move */
}go deeper
Know that 100vh on mobile can be taller than the visible screen because of the browser toolbars, and that svh, lvh and dvh exist to express which viewport you mean.
Explain that vh is pinned to the large viewport for stability, and define each of the three new units in terms of whether retractable toolbars are counted, shown, or tracked live.
Demonstrate the production judgment: pick svh when clipping is unacceptable and dvh for fixed surfaces, explain the mid-scroll resize cost, and give a progressive-enhancement fallback instead of a JS resize listener.
Own the platform tradeoff the spec is making — stability versus fidelity — and be ready to set a house rule so full-screen surfaces across many teams behave the same, including how far you chase the on-screen-keyboard case.
## What vh actually measures `1vh` is 1% of the viewport's height, so `100vh` is the full viewport height. On a desktop browser that is uncontroversial: the viewport is a stable rectangle and `100vh` matches what you see. On mobile it is not stable. The browser's URL bar and bottom toolbar retract as you scroll down and come back as you scroll up, so the actually-visible area shrinks and grows continuously. If `vh` tracked that live, every scroll gesture would resize every element sized in `vh` and relayout the page mid-scroll — visibly janky. Browsers chose stability instead: `vh` is resolved against the **large viewport**, the size the page would have with all retractable UI hidden, and it does not change while you scroll. The consequence is the bug everyone has shipped at least once. A section styled `height: 100vh` is, on first paint with toolbars visible, *taller than the visible area*. Whatever sits at the bottom — the button, the scroll cue, the form's submit — is below the fold, and users on that device simply never see it. ## The four viewport-percentage families CSS defines four sets of viewport-percentage units. Taking the height axis as the example: - **`vh`** — the UA-defined viewport. In practice on mobile this equals the large viewport. - **`lvh`** — the **large** viewport: retractable UI treated as hidden. The biggest value; content sized to it can be clipped while toolbars are shown. - **`svh`** — the **small** viewport: retractable UI treated as shown. The smallest value; content sized to it always fits, but leaves a gap once the toolbars retract. - **`dvh`** — the **dynamic** viewport: whatever is currently visible. It changes as the toolbars move, so the element always matches the visible area exactly, at the cost of resizing during scroll. Each family covers the whole set of viewport units, not just height: `svw`/`lvw`/`dvw`, plus `svmin`, `svmax`, `lvmin`, `dvmax`, and so on. `vmin`/`vmax` pick the smaller/larger of the two axes, and the prefixed variants do the same within their viewport definition. ```css .hero { min-height: 100svh; /* never clipped */ } .app-shell { height: 100dvh; /* tracks the visible area as toolbars move */ } ``` ## Choosing between them **`svh`** is the conservative choice. Nothing is ever cut off, on any device, in any toolbar state. The tradeoff is dead space: once the toolbars retract, your "full screen" panel is short of the bottom edge. For a hero with a critical action inside it, that tradeoff is usually correct. **`dvh`** gives a genuinely full-bleed panel at every moment, which is what an app shell or a full-screen overlay usually wants. The cost is real: because the value changes while scrolling, anything sized in `dvh` resizes during the gesture, which can shift content below it and looks unsettling if it applies to a long scrolling column. Use it for fixed or non-scrolling surfaces — overlays, drawers, an app frame with its own internal scroller — rather than for elements in a long document flow. **`lvh`** is rarely what you want explicitly; it is mostly useful when you deliberately want the old `vh` behaviour and want to say so in the code. A common pattern is to combine `svh` for guaranteed-visible content with `dvh` for the frame around it, or to use `min-height: 100svh` so the panel is at least the safe height but can grow with content. ## Other things vh does not account for - **The on-screen keyboard.** None of these units track a virtual keyboard opening by default; a focused input in a `100dvh` shell can end up behind the keyboard. That is a separate platform concern, not something a unit choice fixes. - **Scrollbars.** `100vw` is 1% of the viewport width *including* a classic (space-taking) scrollbar, which is why `width: 100vw` on a page with a vertical scrollbar produces a small horizontal overflow. Using `width: 100%` on a block-level element avoids it, since percentages resolve against the containing block, not the viewport. ## Support and how to say it in an interview `svh`, `lvh`, `dvh` and their siblings shipped across the major engines during 2022 — Safari 15.4 in March, Firefox 101 in May, Chrome 108 in November — so they are safe to use today without a fallback in most projects. If you still support an older baseline, the graceful pattern is a `vh` declaration followed by the `dvh` one, since a browser that does not understand the second value simply drops that declaration: ```css .panel { height: 100vh; /* fallback */ height: 100dvh; /* wins where supported */ } ``` The answer an interviewer is listening for is not the unit list — it is that you know *why* `vh` is stable (to avoid relayout during scroll), and that you can articulate the clipping-versus-resizing tradeoff between `svh` and `dvh` rather than reaching for a JavaScript workaround.
- Why not just use dvh everywhere, since it always matches what is visible?Because it re-resolves while the toolbars move, so anything sized in `dvh` resizes mid-scroll and shifts the content around it. That is fine for a fixed overlay or an app frame with its own scroller, and unpleasant for sections inside a long scrolling document. `svh` trades a little dead space for complete stability.
- Why does width: 100vw sometimes produce a horizontal scrollbar?Viewport width units include the space taken by a classic vertical scrollbar, while the block-level content area does not. So on a page that scrolls vertically, `100vw` is wider than the available content width by the scrollbar's thickness and overflows horizontally. Using `width: 100%` on a block-level element sidesteps it, since percentages resolve against the containing block.
- Do these units account for the on-screen keyboard opening?No. The dynamic viewport tracks retractable browser UI, not a virtual keyboard, so a focused input inside a `100dvh` shell can still end up hidden behind the keyboard. Handling that is a platform concern beyond the unit choice — the correct interview answer is to name the limitation rather than claim `dvh` solves it.
saying these in an interview costs you the question
- Says vh is simply buggy on mobile browsers
- Reaches for a JavaScript resize handler as the first answer
- Thinks dvh has no downside and should replace vh everywhere
- Confuses svh with the small end of a media query breakpoint
- Claims 100vw and 100% are always interchangeable