Command Palette

Search for a command to run...

Hectal
PHASE 3Beginner ~21 min· topic 3 of 7

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. 100svh always fits.
lvh
Large viewport height unit: 1% of the height when the bars are hidden. The old vh behaviour 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. 100vw includes 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.csswhole filecss
.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).

Meet the mobile address bardiagram
Rendering diagram…

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.csswhole filecss
.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%.

banner.csswhole filecss
/* causes sideways scrolling where scrollbars take space */
.banner { width: 100vw; }

/* full width of the page, no overflow */
.banner { width: 100%; }

Try it yourself

  1. 1

    Swap svh for vh and percent

    In the hero example, delete both min-height lines and write min-height: 100%;. Predict what happens before running. The hero shrinks to its content: 100% refers to the body's height, which is auto. Put min-height: 100svh; back.

  2. 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. 3

    Test on a real phone

    Open any page with a 100vh hero on your phone, then try one with 100svh. Scroll a little so the address bar hides and shows. Notice which one hides its bottom edge on first load.

Code & diagrams

A full-screen hero that fits on phones Baseline 2022+ New tab

Here the viewport is the preview frame. Drag the split to resize it and watch the hero follow the frame's height.

Preview

Press Run to see the page.

Console

Nothing logged yet.
Measure the viewport units live New tab

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

Press Run to see the page.

Console

Nothing logged yet.
The viewport unit familiescss
/* 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.

terminal
$ Look at the bottom of the window
── what you'll see ──
A horizontal scrollbar appears and the page can be dragged about 17px sideways.

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.

terminal
$ Load the page on a phone
── what you'll see ──
The bottom of the hero, with the button, is hidden behind the browser's toolbar until you scroll.

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 dvh on every frame of the toolbar animation; browsers may update it once the toolbar settles. This is one more reason to avoid tying long layouts to dvh.

  • ▸

    scrollbar-gutter: stable on 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 what 100vw measures.

  • ▸

    Inside a container query (CSS Phase 10), cqw/cqh/cqi are 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. 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. 2

    **1vw is 1% of the viewport's width and 1vh** is 1% of its height. So width: 50vw is half the window wide and min-height: 100vh is at least the full window tall, whatever the parent is. That is the big difference from %, which measures against the parent. **vmin is 1% of the smaller side and vmax** 1% of the larger side, so width: 80vmin makes a square that always fits on screen, portrait or landscape.

  3. 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 100vh as the *largest* possible height (bar hidden). A height: 100vh hero section was therefore taller than the screen when the page first loaded, and its bottom (often a button) was hidden behind the bar.

  4. 4

    Three newer families fix this. **svh (small viewport height) is 1% of the height with all browser bars shown**: 100svh always fits on screen. **lvh (large) is 1% of the height with the bars hidden**, the old vh behaviour. **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 as vh.

  5. 5

    Which to use: 100svh for a first screen that must fit completely, such as a hero with a button at the bottom. 100dvh for full-screen apps and dialogs that should always fill exactly what is visible; but because dvh changes while scrolling, content sized with it can jump, so don't use it for long scrolling layouts. Plain vh is still fine on desktop-only layouts and as a fallback line for very old browsers.

  6. 6

    Two traps. First, 100vw includes the vertical scrollbar on systems where scrollbars take space (Windows, Linux, macOS with a mouse), so width: 100vw is wider than the page and causes a horizontal scrollbar; use width: 100% for full-width blocks. Second, never size text only in vw: font-size: 4vw doesn't grow when the reader zooms, which fails the accessibility rule that text must zoom to 200%. Mix it with rem inside clamp() (Topic 3.7).

Explain it without notes

01

What is the difference between width: 50% and width: 50vw?

02

Explain the mobile address bar problem and how svh, lvh and dvh solve it.

03

Why can width: 100vw cause a horizontal scrollbar?

04

Why is font-size: 5vw on its own an accessibility problem?

Practice

01

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.

02

Make a square .avatar that is 40% of the window's smaller side.

Trade-offs

  • ↔

    svh never hides content but leaves a small gap when the bars hide; lvh fills the screen when scrolled but is cut off at first; dvh is 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/vmax to make shapes that always fit.

  • I can choose between svh, lvh and dvh and write a vh fallback.

  • I can spot and fix the 100vw horizontal scrollbar bug.

  • I know why text sized only in vw breaks zoom.