Ad – 728×90
📱 Responsive Design

CSS Viewport Units – vw, vh, vmin, vmax, svh, dvh

Viewport units let you size elements relative to the browser window itself — not their parent container, not the root font size, but the actual visible area. They are essential for full-screen sections, fluid typography, and layouts that must fill exactly the visible screen. But they come with a well-known quirk on mobile browsers, and modern CSS has introduced new units specifically to solve that problem. In this lesson you will learn every viewport unit, when to use each one, and the mobile gotchas to watch for.

⏱️ 16 min read 🎯 Beginner 📅 Updated 2026 👁️ Lesson 4 of 4

The Classic Viewport Units

CSS – Classic viewport units
/* vw — viewport width */
/* 1vw = 1% of viewport width */
.full-width { width: 100vw; }      /* always the full viewport width */
.half-width { width: 50vw; }
h1 { font-size: 5vw; }             /* scales with viewport width */

/* vh — viewport height */
/* 1vh = 1% of viewport height */
.full-height { height: 100vh; }    /* full screen height */
.hero { min-height: 100vh; }       /* at least full screen tall */
.half-screen { height: 50vh; }

/* vmin — 1% of the SMALLER viewport dimension */
/* On portrait phone (360×800): 1vmin = 3.6px */
/* On landscape (800×360): 1vmin = 3.6px */
.square { width: 50vmin; height: 50vmin; } /* always fits screen */

/* vmax — 1% of the LARGER viewport dimension */
/* Useful for elements that should always be prominent regardless of orientation */
.overlay { font-size: 8vmax; }

The 100vh Mobile Problem

On mobile browsers, 100vh is calculated as if the browser chrome (address bar, navigation bar) doesn't exist. When the address bar is visible, 100vh is taller than the actual visible area — causing overflow.

⚠️
100vh overflows on mobile Safari and Chrome

Mobile browsers hide/show the address bar as you scroll. Chrome on Android and Safari on iOS calculate 100vh as the maximum viewport height (address bar hidden). When the address bar is visible, the page is taller than the screen. This causes "above the fold" content to actually be below the fold — a classic mobile layout bug.

CSS – 100vh problem and legacy workarounds
/* PROBLEM: overflows when mobile address bar is visible */
.hero { height: 100vh; }

/* LEGACY WORKAROUND: JavaScript fixes the real viewport height */
/* In JS: document.documentElement.style.setProperty('--vh', window.innerHeight * 0.01 + 'px'); */
/* In CSS: */
.hero { height: calc(var(--vh, 1vh) * 100); }

/* SIMPLE WORKAROUND: use min-height instead of height */
.hero { min-height: 100vh; }   /* still works, slightly different behavior */

Modern Viewport Units (2022+)

CSS now has three new families of viewport units specifically designed to solve the mobile browser chrome problem. Supported in all modern browsers since 2022–2023.

CSS – Modern viewport units
/* ─── Small Viewport (svh, svw, svmin, svmax) ───
   Assumes the largest possible browser chrome (address bar visible).
   Always fits in the visible area — may feel smaller than expected.
   Use when: you need the element to ALWAYS be fully visible */

.modal { height: 100svh; }  /* never clipped by browser chrome */
.banner { height: 20svh; }


/* ─── Large Viewport (lvh, lvw, lvmin, lvmax) ───
   Assumes no browser chrome (address bar hidden, maximum space).
   Same as old 100vh — can overflow when chrome is visible.
   Use when: you're okay with overflow or scrolling */

.hero-section { min-height: 100lvh; }


/* ─── Dynamic Viewport (dvh, dvw, dvmin, dvmax) ───
   Updates in real time as browser chrome shows/hides.
   Causes reflow on scroll (a potential performance concern).
   Use when: you want to perfectly fill the visible screen at all times */

.fullscreen-app { height: 100dvh; }
.sticky-footer  { bottom: env(safe-area-inset-bottom); }


/* ─── Practical recommendation ─── */
/* For hero sections */
.hero { min-height: 100svh; }   /* safe: always fits */

/* For full-screen overlays/modals */
.modal { height: 100dvh; }      /* accurate: updates with chrome */

/* For app-shell layouts */
.app { height: 100dvh; }        /* fills current visible area */
Ad – 336×280

Viewport Units for Fluid Typography

Viewport units scale text with the browser window — useful for large display headings:

CSS – Fluid typography
/* Simple fluid heading — scales with viewport width */
h1 { font-size: 5vw; }
/* Problem: too small on mobile (5vw of 375px = 18.75px), too large on 4K */

/* Better: clamp() with viewport unit in the middle */
h1 { font-size: clamp(2rem, 5vw, 4rem); }
/*              min   preferred  max
   min-width mobile: 2rem (32px)
   scales up with viewport using 5vw
   caps at 4rem (64px) on large screens */

/* Full type scale with clamp */
h1 { font-size: clamp(2rem,   5vw + 1rem, 4rem);   }
h2 { font-size: clamp(1.5rem, 3vw + 1rem, 2.5rem); }
h3 { font-size: clamp(1.2rem, 2vw + 1rem, 2rem);   }
p  { font-size: clamp(1rem,   1.5vw, 1.25rem);      }

/* The + 1rem part prevents the minimum from being too small on very narrow screens */

Practical Use Cases

CSS – Viewport unit patterns
/* 1. Full-screen hero section */
.hero {
  min-height: 100svh;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* 2. Sticky full-screen sidebar on desktop */
.sidebar {
  position: sticky;
  top: 0;
  height: 100svh;
  overflow-y: auto;
}

/* 3. Modal that fits the screen */
.modal {
  max-height: 90svh;  /* leave 10% gap */
  overflow-y: auto;
}

/* 4. Full-width breakout image in article */
.full-bleed {
  width: 100vw;
  margin-left: calc(-50vw + 50%);  /* center-aligned full-bleed */
}

/* 5. Safe area insets (iPhone notch/home indicator) */
.sticky-nav {
  padding-bottom: env(safe-area-inset-bottom);
}

/* 6. Fluid gap/spacing */
.section {
  padding: clamp(40px, 8vw, 120px) clamp(16px, 5vw, 80px);
}

/* 7. Responsive container without media queries */
.container {
  width: min(100% - 2rem, 1200px);  /* min() chooses the smaller value */
  margin: 0 auto;
}

Safe Area Insets – Handling Notches

CSS – Safe area insets
/* Add to <meta viewport> to enable safe area support */
/* <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"> */

/* Use env() to access safe area values */
.bottom-bar {
  padding-bottom: env(safe-area-inset-bottom);  /* home indicator on iPhone */
}

.top-bar {
  padding-top: env(safe-area-inset-top);       /* notch/dynamic island */
}

/* Combined with regular padding */
.nav {
  padding: 16px;
  padding-left: max(16px, env(safe-area-inset-left));
  padding-right: max(16px, env(safe-area-inset-right));
  padding-bottom: max(16px, env(safe-area-inset-bottom));
}

/* Or use padding shorthand with env() fallback */
.safe-wrapper {
  padding:
    env(safe-area-inset-top)
    env(safe-area-inset-right)
    env(safe-area-inset-bottom)
    env(safe-area-inset-left);
}

📋 Summary

  • vw — 1% of viewport width. 100vw = full window width.
  • vh — 1% of viewport height. 100vh buggy on mobile browsers.
  • vmin / vmax — 1% of smaller / larger dimension. Useful for squares that fit any orientation.
  • svh — small viewport height (chrome visible). Safe, never overflows. Use for most things.
  • dvh — dynamic viewport height (updates as chrome shows/hides). Most accurate, reflows on scroll.
  • lvh — large viewport height (chrome hidden). Same as old vh behavior.
  • clamp(min, vw-value, max) — fluid typography that scales without media queries.
  • env(safe-area-inset-*) — padding for iPhone notch/home indicator.
  • min(100% - 2rem, 1200px) — responsive container without media queries.

Frequently Asked Questions

Which unit should I use for full-screen sections — vh, svh, or dvh? +

For hero sections: min-height: 100svh — safe, always fits in the visible area without overflow. For full-screen app layouts (no scroll, like a native app): height: 100dvh — accurately tracks the visible area. For background sections where some overflow is acceptable: min-height: 100vh still works. Avoid lvh for anything that must fit — it's the same as the old vh problem.

Why does 100vw cause a horizontal scrollbar? +

100vw includes the width of the scrollbar (if visible). On Windows, scrollbars are ~17px wide and take space outside the content area. So 100vw on a page with a vertical scrollbar = content width + 17px, causing horizontal overflow. Fix: use width: 100% instead of 100vw for element width. Reserve 100vw for full-bleed effects using negative margins or translate.

What is clamp() and how does it work? +

clamp(min, preferred, max) chooses the preferred value, clamped between min and max. Example: font-size: clamp(1rem, 4vw, 3rem) — normally uses 4vw, but never drops below 1rem and never exceeds 3rem. On a 375px screen: 4vw = 15px (above 1rem minimum, so uses 15px). On a 1200px screen: 4vw = 48px (hits max of 3rem = 48px). It's fluid scaling with guardrails.

What are safe area insets and when do I need them? +

Safe area insets are the regions of the screen obscured by hardware notches, rounded corners, and home indicators on iPhones and some Android devices. You need them when: (1) building a fullscreen experience with viewport-fit=cover in the viewport meta tag, (2) creating a fixed bottom navigation bar, or (3) a fixed top bar. Without accounting for them, your content may be hidden behind the notch or home indicator. Use env(safe-area-inset-bottom) etc. to add the correct padding.