Topic 3.3
Viewport Units
In one line
Viewport units size things as a percentage of the browser window: vw and vh for width and height, vmin/vmax for the smaller or larger side, and svh, lvh and dvh to cope with mobile address bars that appear and disappear.
Think of it like this
A poster that must fill the shop window. Whatever the size of the window, you want the poster to cover exactly all of it, or exactly half of it. Viewport units let you say "half of the window's width" instead of guessing a number of pixels.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Viewport
- The area of the browser window where the page is drawn, not counting the browser's own bars.
- vw / vh
- 1% of the viewport's width / 1% of its height.
- vmin / vmax
- 1% of the viewport's smaller side / 1% of its larger side.
- svh
- Small viewport height unit: 1% of the height when the browser's bars are showing.
100svhalways fits. - lvh
- Large viewport height unit: 1% of the height when the bars are hidden. The old
vhbehaviour on phones. - dvh
- Dynamic viewport height unit: 1% of whatever height is visible right now; it changes as the bars move.
- Fallback
- A safer value written first, so browsers that don't understand the newer value still get something.
- Scrollbar gutter
- The strip of the window a classic scrollbar takes up.
100vwincludes it,100%does not.
Step by step
01vw and vh measure the window, not the parent
A child with width: 50% is half its parent. A child with width: 50vw is half the window, even if its parent is a narrow column. That makes viewport units ideal for things tied to the screen: full-screen sections, sticky headers, modal sizes.
.hero {
min-height: 100vh; /* at least one full screen tall */
padding: 5vh 5vw; /* spacing that scales with the window */
}02vmin and vmax for shapes that must fit
80vmin is 80% of whichever side is shorter. On a 1000×600 window that is 480px; turn the phone to portrait (600×1000) and it is still 480px. A square of 80vmin × 80vmin always fits without scrolling.
03Meet the mobile address bar
On a phone, the visible height changes as the browser bars slide in and out. The three viewport sizes name the states: small (bars shown), large (bars hidden), and dynamic (right now).
04Write a fallback first
The cascade helps you support older browsers: write the old unit first and the new one second. A browser that understands svh uses the second line; an old browser drops the second line as invalid and keeps the first (Topic 0.3 showed that invalid declarations are ignored).
.hero {
min-height: 100vh; /* old browsers */
min-height: 100svh; /* modern browsers: always fits with bars shown */
}05Avoid the 100vw scrollbar trap
On Windows with a normal mouse, the vertical scrollbar takes about 15–17px. width: 100vw adds that width on top of the page, so a horizontal scrollbar appears. Block elements are already full width; if you need to set it, use width: 100%.
/* causes sideways scrolling where scrollbars take space */
.banner { width: 100vw; }
/* full width of the page, no overflow */
.banner { width: 100%; }Try it yourself
- 1
Swap svh for vh and percent
In the hero example, delete both
min-heightlines and writemin-height: 100%;. Predict what happens before running. The hero shrinks to its content:100%refers to the body's height, which isauto. Putmin-height: 100svh;back. - 2
Turn the square into a responsive one
In the second example, change the square to
80vmin. Resize the preview to be tall and thin, then short and wide. Predict which side controls the size each time. - 3
Test on a real phone
Open any page with a
100vhhero on your phone, then try one with100svh. Scroll a little so the address bar hides and shows. Notice which one hides its bottom edge on first load.
Code & diagrams
Here the viewport is the preview frame. Drag the split to resize it and watch the hero follow the frame's height.
Preview
Console
Nothing logged yet.The red 100vw bar is wider than the green 100% bar: it ignores the body's margin, and may include a scrollbar. Resize the preview and run again.
Preview
Console
Nothing logged yet./* classic small (bars shown) large (bars hidden) dynamic (now) */
/* vw vh svw svh lvw lvh dvw dvh */
/* vmin vmax svmin svmax lvmin lvmax dvmin dvmax */
/* vi vb svi svb lvi lvb dvi dvb */
/* vi / vb = 1% of the inline / block axis (width / height in English text) */Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Full-width banner adds a sideways scrollbar
Give a banner width: 100vw on a page tall enough to scroll, viewed on Windows with a mouse.
Break #2
Hero button hidden on phones
Size a hero with height: 100vh and put its call-to-action button at the bottom. Open it on a phone.
Myth vs fact
Myth
100vh is always exactly the visible screen height.
Fact
On phones it is the height with the browser bars hidden, so it is often taller than what you see. 100svh is the height with the bars shown.
Myth
dvh is always the best choice.
Fact
dvh changes while the user scrolls, which can make content jump and costs layout work. Use svh for layouts and keep dvh for things that must match the visible area exactly, like full-screen overlays.
Myth
width: 100vw and width: 100% are the same on the body.
Fact
100vw includes the vertical scrollbar where scrollbars take space and ignores parent margins; 100% is the parent's content width.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
The spec defines viewport units against the initial containing block, which is sized by the layout viewport. Pinch-zoom changes the visual viewport, not the layout viewport, so viewport units don't change while pinch-zooming. JavaScript can read the visual viewport with
window.visualViewport. - ▸
Changes to the dynamic viewport don't have to update
dvhon every frame of the toolbar animation; browsers may update it once the toolbar settles. This is one more reason to avoid tying long layouts todvh. - ▸
scrollbar-gutter: stableon the root reserves space for a scrollbar even when the page doesn't scroll, so content doesn't shift when a scrollbar appears. It doesn't change what100vwmeasures. - ▸
Inside a container query (CSS Phase 10),
cqw/cqh/cqiare the container-relative cousins of viewport units: 1% of the size of the nearest query container instead of the window. Reach for them when a component should scale with its slot, not the screen.
Remember this
- 1
The viewport is the part of the browser window where the page is drawn: the window minus the address bar, tabs and toolbars. On a phone it is the screen area below (and above) the browser's own bars. In Hectal's live examples, the viewport is the preview frame.
- 2
**
1vwis 1% of the viewport's width and1vh** is 1% of its height. Sowidth: 50vwis half the window wide andmin-height: 100vhis at least the full window tall, whatever the parent is. That is the big difference from%, which measures against the parent. **vminis 1% of the smaller side andvmax** 1% of the larger side, sowidth: 80vminmakes a square that always fits on screen, portrait or landscape. - 3
The mobile address bar problem. On phones, the browser's address bar slides away when you scroll down and comes back when you scroll up, so the visible area changes height. Early on, mobile browsers defined
100vhas the *largest* possible height (bar hidden). Aheight: 100vhhero section was therefore taller than the screen when the page first loaded, and its bottom (often a button) was hidden behind the bar. - 4
Three newer families fix this. **
svh(small viewport height) is 1% of the height with all browser bars shown**:100svhalways fits on screen. **lvh(large) is 1% of the height with the bars hidden**, the oldvhbehaviour. **dvh(dynamic) follows the current** height and changes as the bars move. Each has width (svw,lvw,dvw) and min/max versions (svmin,dvmax…). On a desktop, where no bars move, all three are the same asvh. - 5
Which to use:
100svhfor a first screen that must fit completely, such as a hero with a button at the bottom.100dvhfor full-screen apps and dialogs that should always fill exactly what is visible; but becausedvhchanges while scrolling, content sized with it can jump, so don't use it for long scrolling layouts. Plainvhis still fine on desktop-only layouts and as a fallback line for very old browsers. - 6
Two traps. First,
100vwincludes the vertical scrollbar on systems where scrollbars take space (Windows, Linux, macOS with a mouse), sowidth: 100vwis wider than the page and causes a horizontal scrollbar; usewidth: 100%for full-width blocks. Second, never size text only invw:font-size: 4vwdoesn't grow when the reader zooms, which fails the accessibility rule that text must zoom to 200%. Mix it withreminsideclamp()(Topic 3.7).
Explain it without notes
What is the difference between width: 50% and width: 50vw?
Explain the mobile address bar problem and how svh, lvh and dvh solve it.
Why can width: 100vw cause a horizontal scrollbar?
Why is font-size: 5vw on its own an accessibility problem?
Practice
Make a .modal box that is never more than 90% of the window's width or 80% of the visible height on phones, and centre it with position: fixed.
Make a square .avatar that is 40% of the window's smaller side.
Trade-offs
- ↔
svhnever hides content but leaves a small gap when the bars hide;lvhfills the screen when scrolled but is cut off at first;dvhis always exact but changes size while scrolling. - ↔
Viewport units are great for screen-level layout, but components inside columns usually want
%or container units instead.
Done when you can
I can explain the difference between
%and viewport units.I can use
vmin/vmaxto make shapes that always fit.I can choose between
svh,lvhanddvhand write avhfallback.I can spot and fix the
100vwhorizontal scrollbar bug.I know why text sized only in
vwbreaks zoom.