CSS dvh, svh, lvh: Getting Mobile Viewport Units Right
AI generated
CSS · Mobile Viewport · iOS Safari · Responsive Design
CSS dvh, svh, lvh
Mobile Viewport Units and the iOS 100vh Bug

The notorious iOS Safari bug with 100vh has kept developers busy for years. The modern CSS viewport units dvh, svh and lvh solve the problem permanently, with no JavaScript, no resize events and no fragile workarounds. This article explains how Dynamic, Small and Large Viewport Units work and when you actually need which one.

10 min read dvh · svh · lvh · 100vh · iOS Safari · Dynamic Viewport Chrome 108+ · Safari 15.4+ · Firefox 101+

1. The 100vh Problem on Mobile Browsers

Anyone who has ever built a full-screen hero section or a mobile overlay with height: 100vh knows the problem: on iOS Safari and older Android browsers, the height gets calculated incorrectly. The browser measures the viewport without its own UI chrome, the address bar, tab bar and bottom navigation, which means 100vh ends up larger than the area that is actually visible. The bottom edge of the element disappears behind the browser UI. This is not a bug in the classic sense but a deliberate design choice: Apple wanted a smooth UI animation while scrolling, with the browser chrome sliding in and out. The resulting gap between the CSS viewport height and the visible area has haunted developers since iOS 8.

The classic workaround was JavaScript: a resize event listener sets a CSS custom property based on window.innerHeight, which is then used instead of 100vh. The pattern is well known but fragile. It requires JavaScript at first paint, triggers a layout reflow every time the event fires and stops working if JavaScript is blocked or delayed. The modern CSS dvh, svh and lvh units offer a purely CSS-based solution that removes all of these downsides. They were standardized in the CSS specification in 2022 and are now broadly supported starting with Chromium 108, Safari 15.4 and Firefox 101.

2. svh: Small Viewport Height

svh (Small Viewport Height) matches the height of the viewport when the browser UI is fully expanded, in other words when every bar is visible and the available area is at its smallest. On an iPhone with Safari in its default state, 100svh shows exactly the height that is actually available to the user while the address bar is fully visible. svh is therefore the most conservative option: an element with height: 100svh always fits inside the viewport, no matter what state the browser UI is in.

The unit is a great fit for elements that must be guaranteed to be fully visible, regardless of whether the browser chrome is currently expanded or collapsed. Typical use cases are dialogs, modals and overlays where it is critical that no content disappears behind the browser UI. CSS svh is the safe choice whenever complete visibility matters more than making maximum use of the available space. One downside: as the browser chrome slides in and out, a visible jump can occur, because 100svh is then smaller than the area that is actually available.


/* Small Viewport Height: conservative, always fully visible */
.modal-overlay {
  /* Height based on the smallest possible viewport (UI fully visible) */
  height: 100svh;
  overflow-y: auto;
}

.sticky-footer-nav {
  /* Position relative to small viewport bottom */
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  /* Ensure content above nav is visible within small viewport */
  padding-bottom: env(safe-area-inset-bottom, 0px);
}

/* Minimum height that works in all browser UI states */
.hero-safe {
  min-height: 100svh;
  display: flex;
  align-items: center;
}

/* Container that must not be clipped by browser chrome */
.fullscreen-dialog {
  width: 100svw;
  height: 100svh;
  position: fixed;
  inset: 0;
}

One important difference from 100vh: svh is a static value that does not change while scrolling. The browser calculates it once, based on the smallest possible viewport state, and keeps that value. This prevents layout reflows during scrolling, which makes CSS svh more attractive from a performance standpoint than dvh whenever dynamic adjustment is not actually needed.

3. lvh: Large Viewport Height

lvh (Large Viewport Height) is the opposite of svh: it matches the height of the viewport when the browser UI is fully collapsed, in other words when the maximum amount of space is available for content. This is the state most users see while scrolling through a page: the address bar has disappeared, the bottom tab bar has retracted, and the content fills the entire screen. 100lvh therefore corresponds to what 100vh means on desktop browsers without dynamic chrome.

The use case for CSS lvh is narrower than for svh or dvh. It suits decorative elements such as background images or videos, where a little clipping at the edge is not a problem and where you want to make use of the maximum image area. The catch: on the initial page view, while the browser chrome is still visible, an element with height: 100lvh can extend beyond the visible area. That leads to unwanted scroll behavior or clipped content. lvh is therefore rarely the first choice for interactive or content-bearing elements.

4. dvh: Dynamic Viewport Height

dvh (Dynamic Viewport Height) is the smartest of the three units. It adapts dynamically to the current state of the browser UI: when the address bar is visible, 100dvh equals 100svh; when it is collapsed, it equals 100lvh. That sounds like the ideal solution, and for many use cases it is. The unit behaves the way developers intuitively expect 100vh to behave on mobile devices.

The price for that flexibility is a possible layout reflow during scrolling. When a user scrolls and the browser chrome expands or collapses, the computed value of dvh changes. Elements that use this value for their height get recalculated and possibly re-rendered. For simple containers that is a non-issue. For complex layouts with many dependent sizes it can cause visible flickering or jumping. For hero sections and full-screen elements, where adapting to the current viewport state matters more than layout stability, CSS dvh remains the recommended choice.


/* Dynamic Viewport Height: adapts to current browser UI state */
.hero-section {
  /* Fills exactly the visible area, regardless of browser chrome state */
  min-height: 100dvh;
  display: grid;
  place-items: center;
}

/* Fullscreen mobile menu */
.mobile-nav-overlay {
  position: fixed;
  inset: 0;
  /* Dynamic: correct height when menu opens mid-scroll */
  height: 100dvh;
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* Large Viewport for decorative backgrounds */
.bg-video-container {
  /* Use lvh for decorative elements where overflow is acceptable */
  min-height: 100lvh;
  position: relative;
  overflow: hidden;
}

/* Progressive enhancement: cascade from old to new */
.fullscreen-element {
  height: 100vh;           /* Fallback for old browsers */
  height: 100dvh;          /* Dynamic viewport: modern browsers */
}

/* Fluid typography scaled to viewport height */
.hero-headline {
  font-size: clamp(2rem, 8dvh, 5rem);
}

5. Viewport Widths: svw, lvw, dvw

The same three-way split exists for width too: svw, lvw and dvw mirror the same concepts on the horizontal axis. In practice, the difference between the three width variants is less noticeable on most devices, since browsers rarely expand and collapse their horizontal chrome dynamically. The main exceptions are browsers with side panels or certain PWA modes. Still, the full set exists: svw, lvw, dvw as well as the inline and block variants svi, lvi, dvi, svb, lvb, dvb.

Especially relevant for modern layouts is combining dvh and dvw for truly full-screen elements. With width: 100dvw; height: 100dvh; and position: fixed; inset: 0; you get an element that exactly covers the visible area, at any point in time and in any browser UI state. The new units also complement the existing vmin and vmax variants, which now have counterparts too: svmin, lvmin, dvmin as well as svmax, lvmax and dvmax.

6. Practical Examples: Hero Sections and Modals

The most common use cases for CSS dvh, svh and lvh are full-screen hero sections, mobile navigation overlays and modals. For hero sections, min-height: 100dvh is the recommended choice: the element fills the visible area in every state but still grows if the content needs more space. For modals that absolutely must be fully visible, height: 100svh is the safest choice, since it stays correct even while the browser chrome is visible.

Another important use case is calculating sticky elements and scroll-snap containers. With CSS dvh you can build scroll-snap sections that always occupy exactly the visible area, regardless of whether the address bar happens to be visible or not. That produces a noticeably more consistent scroll experience on mobile than the JavaScript-based approach.


/* Scroll-snap fullscreen sections using dvh */
.snap-container {
  height: 100dvh;
  overflow-y: scroll;
  scroll-snap-type: y mandatory;
  overscroll-behavior-y: contain;
}

.snap-section {
  height: 100dvh;           /* Each section fills exactly the visible viewport */
  scroll-snap-align: start;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

/* Modal: always fully visible, even when browser chrome is showing */
.modal-wrapper {
  position: fixed;
  inset: 0;
  height: 100svh;           /* svh: conservative, never clipped by browser UI */
  display: grid;
  place-items: center;
  background-color: rgb(0 0 0 / 0.6);
}

.modal-content {
  max-height: calc(100svh - 4rem);  /* Leave breathing room inside safe viewport */
  overflow-y: auto;
  border-radius: 1rem;
}

/* Sticky sidebar that respects dynamic viewport */
.sidebar {
  position: sticky;
  top: 0;
  height: 100dvh;
  overflow-y: auto;
}

7. Fallback Strategies and Progressive Enhancement

Even though CSS dvh, svh and lvh are now supported by all modern browsers, a fallback strategy for older browsers is still worthwhile. The simplest pattern is a cascade: declare the old unit first, then the new one right after. Browsers that don't recognize the new unit simply ignore it and use the fallback. Browsers with support override the value. This pattern works with no feature detection and no JavaScript.

For projects that still need to support much older browsers, the well-established CSS custom property trick is available as extra insurance. The custom property --vh gets set via JavaScript to window.innerHeight * 0.01 and is then used as calc(var(--vh, 1vh) * 100). This approach produces correct values on every device but carries the downside of a JavaScript dependency. With CSS dvh as the primary declaration and 100vh as the fallback, this requirement disappears entirely for most current project needs.

8. Viewport Units Compared Side by Side

Choosing the right CSS viewport unit depends on the concrete use case. The table below summarizes the key differences and gives recommendations for typical scenarios.

Unit Based On Layout Reflow Best Used For
100vh UA-dependent (often without chrome) No Desktop, fallback for old browsers
100svh Smallest viewport (UI visible) No Modals, dialogs, critical overlays
100lvh Largest viewport (UI hidden) No Decorative backgrounds, images
100dvh Current viewport (dynamic) Possible while scrolling Hero sections, scroll-snap, sidebars
JS --vh window.innerHeight On resize event Legacy browsers as a last resort

For most modern projects, min-height: 100dvh is the recommended default for full-screen elements on mobile. CSS svh comes into play whenever guaranteed full visibility matters more than making exact use of the available space. lvh remains a special case for decorative elements where minor overflow is acceptable.

9. Performance and Layout Stability

From a performance standpoint, CSS dvh is the only one of the new units that can trigger recalculations while scrolling. When the browser chrome expands or collapses, every element using dvh-based values has to be recalculated. On modern devices and browsers this overhead is minimal and barely measurable in practice. That said, if you want to avoid layout instability altogether, prefer svh or lvh, depending on which viewport state should be the basis.

In the context of Core Web Vitals, Cumulative Layout Shift (CLS) is especially relevant here. Elements whose height is driven by dvh and that don't yet have a stable size during the initial load phase can contribute to CLS. The solution is to set the initial value with svh and only use dvh for state changes after the first paint, for example via a CSS class applied after the initial load. For most hero sections this extra effort is not necessary, since min-height: 100dvh without a hard fixed size is CLS-neutral.

Mironsoft

Mobile-first CSS, responsive design and performance optimization

Want mobile layouts that look right on every device?

We audit existing layouts for mobile viewport issues, replace fragile workarounds with modern CSS solutions and make sure hero sections, modals and full-screen elements render correctly on iOS Safari, Android Chrome and every modern browser.

Viewport Audit

Analysis of every vh usage and identification of iOS Safari issues in your existing code

CSS Migration

Replacing JS workarounds with dvh, svh and lvh, complete with correct fallbacks

CLS Optimization

Reducing layout shift through correct viewport units and stable initial layouts

10. Summary

The three modern CSS viewport units dvh, svh and lvh solve the long-standing 100vh problem on mobile browsers for good, with no JavaScript required. dvh adapts dynamically to the current viewport state and is the best choice for hero sections and scroll-snap layouts. svh is based on the smallest possible viewport and guarantees that elements are always fully visible, making it ideal for modals and critical overlays. lvh uses the largest viewport and suits decorative background elements where minor overflow is acceptable.

The recommended fallback strategy is simple: declare 100vh first, then 100dvh on the next line. Older browsers ignore the unknown unit; modern browsers override the value. For most modern projects, min-height: 100dvh is the only change needed to eliminate iOS Safari 100vh issues: a single line of CSS replaces a whole chunk of JavaScript.

CSS dvh, svh, lvh: The Essentials at a Glance

dvh: Dynamic Viewport

Adapts to the browser chrome state. Best choice for hero sections and scroll-snap. Possible reflow while scrolling.

svh: Small Viewport

Based on the smallest viewport (chrome visible). Guarantees full visibility. Ideal for modals and overlays.

lvh: Large Viewport

Based on the largest viewport (chrome hidden). Suits decorative backgrounds. Can extend beyond the visible area.

Fallback Pattern

height: 100vh; height: 100dvh; the cascade is enough. No JavaScript needed for modern browser support.

11. FAQ: CSS dvh, svh, lvh and Mobile Viewport

1What is the difference between dvh and 100vh?
100vh often ignores the browser chrome on mobile browsers. dvh adapts dynamically: it takes the svh value when the chrome is visible and the lvh value when the chrome is hidden.
2When should you use svh instead of dvh?
Whenever guaranteed full visibility matters more than making maximum use of the space: modals, dialogs, critical overlays.
3Does dvh cause layout reflows?
Potentially while scrolling, when the browser chrome expands or collapses. In practice barely noticeable on modern devices.
4Do you still need the JS 100vh workaround?
Not for Chrome 108+, Safari 15.4+, Firefox 101+. The CSS cascade pattern height: 100vh; height: 100dvh; is enough.
5What is lvh and when should you use it?
lvh is based on the largest viewport (chrome hidden). For decorative backgrounds where slight overflow is acceptable.
6Are there also svw, lvw and dvw?
Yes, a full set exists for width, inline axis and block axis. For width, the difference between variants is usually small in practice.
7Fallback for older browsers?
height: 100vh; height: 100dvh; the cascade is enough. Older browsers use vh, modern browsers override it with dvh. No JS needed.
8Does dvh affect the CLS score?
With min-height instead of height, CLS is generally not an issue, since min-height allows growth without shifting content below it.
9Does dvh work in PWAs?
Yes. In full-screen PWA mode, dvh, svh and lvh all produce the same value. In browser-based PWA mode, dvh behaves correctly and dynamically.
10Best CSS value for a hero section?
min-height: 100dvh allows growth and adapts to the browser chrome. Add min-height: 100vh; before it as a fallback.