Skip to main content
โšก Calmops

CSS Viewport Units: Mastering Dynamic Viewport Sizes

CSS Viewport Units have evolved significantly with new units like svh (small viewport height), dvh (dynamic viewport height), and lvh (large viewport height). These units solve long-standing issues with mobile browser address bars and create more reliable responsive designs.

The Problem: Traditional Viewport Units

Before these new units, designers struggled with 100vh not actually representing the visible viewport:

/* Traditional - often shows unwanted scrollbars on mobile */
.hero {
  height: 100vh;
  overflow: hidden;
}

The issue: Mobile browsers have dynamic UI elements (address bar, navigation) that change the visible viewport area.

How Mobile Browsers Work

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Browser UI (address)  โ”‚  <- Often hidden
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                         โ”‚
โ”‚    Visible Content      โ”‚  <- What users see
โ”‚                         โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  Browser UI (nav)       โ”‚  <- Often hidden
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

When the user scrolls, the browser UI hides, but 100vh doesn’t adjust:

/* Problem: Content gets hidden behind browser UI */
.hero {
  height: 100vh; /* Fixed - doesn't adapt */
  background: linear-gradient(to bottom, blue, purple);
}

The Solution: Dynamic Viewport Units

CSS now provides multiple viewport units to handle different scenarios:

Viewport Units Comparison

/* Small Viewport Height - smallest dynamic viewport */
height: 100svh;  /* Always fits within visible area */

/* Large Viewport Height - largest dynamic viewport */
height: 100lvh;  /* Includes potentially hidden areas */

/* Dynamic Viewport Height - matches current visible area */
height: 100dvh;  /* Adapts as UI shows/hides */

/* Traditional - problematic on mobile */
height: 100vh;   /* Fixed, doesn't adapt */

Small Viewport Height (svh)

The svh unit represents the smallest viewport height when all browser UI is visible:

/* Guaranteed to be visible without scrolling */
.hero {
  height: 100svh;
  display: flex;
  align-items: center;
  justify-content: center;
}

When to Use svh

  • Hero sections that should always be fully visible
  • Full-screen modals and dialogs
  • Landing pages
  • Any element that shouldn’t scroll when first viewed
/* Full-screen modal - always visible */
.modal-overlay {
  position: fixed;
  inset: 0;
  height: 100svh;
  width: 100svw;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.5);
}

Large Viewport Height (lvh)

The lvh unit represents the largest viewport height when browser UI is hidden:

/* May have parts hidden initially */
.hero {
  height: 100lvh;
}

When to Use lvh

  • Background elements that can extend beyond initial view
  • Scroll-triggered animations
  • Elements designed to scroll into view
/* Background that extends fully */
.page-background {
  height: 100lvh;
  width: 100%;
  position: fixed;
  top: 0;
  left: 0;
  background: linear-gradient(180deg, #1a1a2e, #16213e);
  z-index: -1;
}

Dynamic Viewport Height (dvh)

The dvh unit dynamically matches the current visible viewport as browser UI shows/hides:

/* Automatically adapts to UI state */
.hero {
  height: 100dvh;
  transition: height 0.3s ease;
}

With Scroll

/* Content that fills viewport while scrolling */
.fullscreen-section {
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}

/* Works smoothly as address bar shows/hides */
.main-content {
  flex: 1;
  overflow-y: auto;
}

Viewport Width Units

Similar units exist for width:

/* Viewport Width */
width: 100vw;     /* Traditional - may cause horizontal scroll */

/* Small Viewport Width */
width: 100svw;    /* Smallest visible width */

/* Large Viewport Width */
width: 100lvw;    /* Largest visible width */

/* Dynamic Viewport Width */
width: 100dvw;    /* Dynamic width */

Practical Examples

Example 1: Mobile-First Hero Section

/* Always visible hero section */
.hero {
  height: 100svh;
  min-height: -webkit-fill-available;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 2rem;
  box-sizing: border-box;
}

.hero h1 {
  font-size: clamp(2rem, 5vw, 4rem);
  margin: 0;
}

.hero p {
  font-size: clamp(1rem, 2.5vw, 1.5rem);
  max-width: 60ch;
}

/* Fallback for older browsers */
@supports not (height: 100svh) {
  .hero {
    min-height: 100vh;
    min-height: 100dvh;
  }
}

Example 2: Fixed Navigation

/* Header that accounts for dynamic viewport */
.header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 60px;
  background: white;
  z-index: 100;
}

.main-content {
  padding-top: 60px;
  /* Ensure content isn't hidden */
  min-height: calc(100svh - 60px);
}

Example 3: Scrollable Content Area

/* Layout with fixed header and scrollable content */
.app-layout {
  display: grid;
  grid-template-rows: auto 1fr;
  height: 100dvh;
  height: 100svh;
  overflow: hidden;
}

.header {
  background: #333;
  color: white;
  padding: 1rem;
}

.content {
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

.content-inner {
  padding: 2rem;
}

Example 4: Split Screen Layout

/* Full-height split screen */
.split-layout {
  display: grid;
  grid-template-columns: 1fr;
  height: 100dvh;
  height: 100svh;
}

@media (min-width: 768px) {
  .split-layout {
    grid-template-columns: 1fr 1fr;
  }
}

.panel {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2rem;
}

Example 5: Card Grid with Viewport Sizing

/* Responsive card grid */
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 1rem;
  padding: 1rem;
  height: calc(100dvh - 100px);
  overflow-y: auto;
}

.card {
  background: white;
  border-radius: 8px;
  padding: 1.5rem;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

Example 6: Full-Screen Video Background

/* Video that always fills visible area */
.video-container {
  position: relative;
  height: 100svh;
  width: 100vw;
  overflow: hidden;
}

.video-bg {
  position: absolute;
  top: 50%;
  left: 50%;
  min-width: 100%;
  min-height: 100%;
  transform: translate(-50%, -50%);
}

.video-overlay {
  position: absolute;
  inset: 0;
  background: rgba(0,0,0,0.4);
  display: flex;
  align-items: center;
  justify-content: center;
}

Viewport Units in CSS Functions

With calc()

/* Calculate heights with viewport units */
.header {
  height: calc(60px + 2svh);
}

.content {
  min-height: calc(100dvh - 60px - 40px);
}

With clamp()

/* Responsive sizing */
.hero-title {
  font-size: clamp(2rem, 5dvh, 5rem);
}

.section {
  min-height: clamp(300px, 50dvh, 600px);
}

With container queries

/* Works with container queries too */
.card-container {
  container-type: inline-size;
}

.card {
  height: 100cqh;  /* Container query height */
}

Legacy Browser Support

Use @supports for progressive enhancement:

/* Modern browsers */
.hero {
  height: 100svh;
}

/* Fallback for older browsers */
@supports not (height: 100svh) {
  .hero {
    height: 100vh;
    min-height: -webkit-fill-available;
    min-height: -moz-available;
  }
}

JavaScript Fallback

// Set CSS custom property for older browsers
function setViewportHeight() {
  const vh = window.innerHeight * 0.01;
  document.documentElement.style.setProperty('--vh', `${vh}px`);
}

// Use in CSS: height: calc(100 * var(--vh));
setViewportHeight();
window.addEventListener('resize', setViewportHeight);

Browser Support

Unit Chrome Firefox Safari Edge
svh 108+ 101+ 15.4+ 108+
dvh 108+ 101+ 15.4+ 108+
lvh 108+ 101+ 15.4+ 108+
/* Feature detection */
@supports (height: 100svh) {
  .hero { height: 100svh; }
}

Common Patterns

Pattern 1: Mobile Full Screen

.fullscreen-mobile {
  height: 100svh;
  height: 100dvh;
  min-height: -webkit-fill-available;
}

Pattern 2: Scroll Container

.scroll-container {
  height: 100dvh;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

Pattern 3: Fixed Background

.fixed-background {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  width: 100svw;
  height: 100vh;
  height: 100dvh;
  z-index: -1;
}
.page-wrapper {
  display: flex;
  flex-direction: column;
  min-height: 100dvh;
  min-height: 100svh;
}

.content {
  flex: 1;
}

.footer {
  margin-top: auto;
}

Best Practices

1. Use svh for Initial View

/* For elements that should be visible immediately */
.hero, .modal, .overlay {
  height: 100svh;
}

2. Use dvh for Scrollable Content

/* For scrollable containers */
.main-content {
  height: 100dvh;
  overflow: hidden;
}

.inner-scroll {
  height: 100%;
  overflow-y: auto;
}

3. Combine with Modern CSS

/* Works great with container queries */
.card-container {
  container-type: inline-size;
}

.card {
  height: 100cqh;
}

@container (max-width: 400px) {
  .card {
    height: 50svh;
  }
}

4. Test on Real Devices

/* Always test on actual mobile devices */
.hero {
  height: 100svh;
  /* Test with and without this */
  min-height: -webkit-fill-available;
}

Debugging Viewport Issues

Chrome DevTools

  1. Open DevTools (F12)
  2. Toggle device toolbar (Ctrl+Shift+M)
  3. Test with address bar shown/hidden
  4. Check computed styles for actual values

Common Issues

/* Issue: Horizontal scroll */
/* Solution: Use 100svw instead of 100vw */
body {
  width: 100svw;
}

/* Issue: Content hidden behind fixed elements */
/* Solution: Account for header height */
.main-content {
  min-height: calc(100svh - 60px);
}

/* Issue: Animation jank */
/* Solution: Use dvh for smooth transitions */
.hero {
  height: 100dvh;
  transition: height 0.3s ease;
}

External Resources

Summary

Modern CSS viewport units solve long-standing mobile layout issues:

  • svh (Small Viewport Height): Use for elements that should always be visible
  • lvh (Large Viewport Height): Use for backgrounds and elements that can extend
  • dvh (Dynamic Viewport Height): Use for scrollable content areas
  • svw/dvw/lvw: Same concepts applied to width
  • Browser Support: Chrome 108+, Firefox 101+, Safari 15.4+, Edge 108+

Start using these units today to create more reliable, mobile-friendly layouts!

Comments