If you have ever set height: 100vh on a hero section and noticed the bottom gets cut off on mobile, you have hit the most common viewport unit problem in CSS. The classic vh unit does not account for the browser's own UI: the address bar, tab strip, and toolbar that appear at the top or bottom of the screen. Three newer units were introduced to solve exactly this: svh, dvh, and lvh.

This guide explains why vh breaks on mobile, what each new unit measures, and exactly when to use each one.

Why 100vh breaks on mobile

On desktop browsers, 100vh reliably equals the full visible height of the browser window. On mobile, the browser has its own UI: an address bar at the top, and sometimes a tab bar or navigation bar at the bottom. These elements sit inside the screen but on top of your page.

The problem is how browsers measure the viewport when the page first loads. The browser UI is visible, which means the actual visible page area is shorter than the full screen. But 100vh is calculated as if the browser UI were already hidden, matching the full screen height instead of the visible height. The result: your 100vh element is taller than what the user can see, and the bottom of your content is hidden underneath the browser toolbar.

To make things worse, when the user scrolls down, mobile browsers retract their UI to give more reading space. At that point the visible area grows, but your 100vh section has already been sized, so you get a layout jump as the browser updates.

This is not a bug. The original vh behaviour was a deliberate choice, locking the unit to a stable value so it does not constantly resize as the browser UI shows and hides during scrolling. The new units give you explicit control over which measurement you actually want.

The three new units: svh, dvh and lvh

CSS introduced a new family of dynamic viewport units built around one key concept: the viewport has a small state (browser UI fully visible) and a large state (browser UI fully hidden). The three new height units each correspond to a specific state.

If you're just comparing vh vs dvh vs svh vs lvh at a glance: vh is fixed and can overflow the visible screen on mobile, svh is the safe minimum that never overflows, lvh is the maximum once the browser UI is gone, and dvh moves live between the two as the user scrolls.

UnitWhat it measuresUpdates live?
svhSmall viewport height: the viewport with browser UI fully visibleNo
lvhLarge viewport height: the viewport with browser UI fully hiddenNo
dvhDynamic viewport height: the actual current visible heightYes

svh is the smallest the viewport will ever be on that device. lvh is the largest it will ever be. dvh sits somewhere between the two and updates in real time as the browser UI appears and disappears.

svh: small viewport height

svh measures the viewport height when the browser UI is fully expanded and taking up its maximum space. This is what the user sees when they first land on the page, before any scrolling. 100svh will never be taller than the visible area, which means content sized with svh will never overflow off the bottom of the screen.

.hero {
  min-height: 100svh; /* never cut off by mobile browser UI */
}

.full-screen-modal {
  height: 100svh;
  overflow-y: auto;
}

The tradeoff with svh is that on devices where the browser UI retracts when scrolling, a 100svh section will have a small gap at the bottom once the UI hides. The section does not grow to fill the newly available space. For most designs this is acceptable. A small gap is far less disruptive than content being hidden entirely.

svh is the right default for most full-height layouts. Content is always fully visible on load, and the gap that appears when the browser UI retracts is small and rarely noticeable.

lvh: large viewport height

lvh measures the viewport height when the browser UI is fully hidden, the maximum available space. 100lvh equals what 100vh on desktop would give you: the full screen from top to bottom, ignoring any browser chrome.

.canvas {
  height: 100lvh; /* fills screen when browser UI is retracted */
}

On mobile, 100lvh will be taller than the visible area when the browser UI is showing, which is exactly the same problem as 100vh. The main use case for lvh is as a ceiling value: "this element should never exceed the full screen height even when the browser UI is gone." On its own it is rarely what you want for a hero or modal.

dvh: dynamic viewport height

dvh tracks the actual visible viewport height in real time. When the browser UI is visible, 100dvh equals 100svh. When the browser UI hides, 100dvh grows to equal 100lvh. It is always exactly the visible screen height at any given moment.

.hero {
  min-height: 100dvh; /* always exactly the visible screen */
}

This sounds perfect, but the constant resizing comes with a cost. Any time the browser UI shows or hides, every element sized with dvh has to recalculate and repaint. In most designs this causes a visible layout shift: the hero section grows slightly when the user starts scrolling down, and shrinks back when they scroll up. For a simple hero this may be subtle, but for a layout with sticky headers, overlapping sections, or fixed-position elements, the constant reflow is visible and jarring.

Test dvh on a real device before shipping. The constant resize can cause noticeable layout jumps on scroll, especially when other fixed or sticky elements are present. If it causes jank, switch to svh.

The full family: vi, si, di and the width equivalents

The same small/large/dynamic concept applies to all viewport dimensions, not just height. The complete set of new units:

UnitMeasuresEquivalent to
svh / lvh / dvhViewport heightSmall / large / dynamic
svw / lvw / dvwViewport widthSmall / large / dynamic
svmin / lvmin / dvminSmaller of width or heightSmall / large / dynamic
svmax / lvmax / dvmaxLarger of width or heightSmall / large / dynamic
svi / lvi / dviViewport inline size (width in horizontal writing)Small / large / dynamic
svb / lvb / dvbViewport block size (height in horizontal writing)Small / large / dynamic

In practice, svh, dvh, and lvh are what you will encounter and need. Mobile browsers resize the viewport vertically when their UI appears, not horizontally, so the width variants behave the same as the classic vw in most cases.

Writing the fallback for older browsers

Browser support for the new units is above 90% globally as of 2026, but some users are still on older versions of Chrome, Firefox, or Samsung Internet that do not support them. The correct fallback pattern is to write the vh value first and the new unit second. Browsers that do not recognise svh will ignore it and use the vh declaration above.

.hero {
  min-height: 100vh;   /* fallback for older browsers */
  min-height: 100svh;  /* used by browsers that support it */
}

This pattern works because CSS declarations in the same rule overwrite each other in source order. A browser that understands svh reads both lines and uses the second. A browser that does not understand svh ignores the second line and keeps the first.

Browser support

BrowserSupport from version
Chrome108
Safari15.4
Firefox101
Edge108
Samsung Internet21
iOS Safari15.4

Safari 15.4 shipped in March 2022 and iOS 15.4 followed the same month, so iOS Safari has had support for over three years. The fallback pattern above covers any remaining users on older versions.

Common patterns

Full-height hero section

.hero {
  min-height: 100vh;          /* fallback */
  min-height: 100svh;         /* safe on mobile */
  display: flex;
  align-items: center;
  justify-content: center;
}

Full-screen modal or drawer

.modal {
  position: fixed;
  inset: 0;
  height: 100vh;             /* fallback */
  height: 100dvh;            /* fills visible area exactly */
  overflow-y: auto;
}

For a fixed-position modal, dvh is appropriate because the modal is already taking over the full screen and there is nothing behind it to shift around. The constant resize is not noticeable inside a fixed overlay.

Sticky sidebar that fills the screen

.sidebar {
  position: sticky;
  top: 0;
  height: 100vh;             /* fallback */
  height: 100svh;            /* stable height on mobile */
  overflow-y: auto;
}

Minimum height with content that may grow

.section {
  /* min-height lets content push the section taller if needed */
  min-height: 100vh;
  min-height: 100svh;
  padding: 4rem 2rem;
}

Using all three together for a precise range

.panel {
  /* Never shorter than the small state */
  min-height: 100svh;
  /* Never taller than the large state */
  max-height: 100lvh;
  /* Tries to match the actual visible height */
  height: 100dvh;
}

Full height minus a fixed header

.content {
  min-height: calc(100vh - 4rem);   /* fallback */
  min-height: calc(100svh - 4rem);  /* subtract a fixed 4rem header */
}

calc() works with the new units exactly like it does with vh. This is useful any time you need to fill the remaining screen below a fixed header or above a fixed footer, subtract the exact pixel or rem value of that element and the rest of the layout stays safe from mobile overflow.

Sticky footer that stays fully visible

.page {
  display: flex;
  flex-direction: column;
  min-height: 100vh;   /* fallback */
  min-height: 100svh;  /* footer never sits below the fold */
}

.page-footer {
  margin-top: auto; /* pushes footer to the bottom on short pages */
}

Using svh instead of vh here means the flex container never claims more height than the visible screen, so the footer stays reachable without scrolling past it on load.

Full-bleed section on a notched phone

.full-bleed {
  height: 100vh;                              /* fallback */
  height: 100dvh;                             /* tracks the visible screen */
  padding-bottom: env(safe-area-inset-bottom); /* clears the home indicator */
}

On notched or gesture-based phones, combine a viewport unit with env(safe-area-inset-*) so content does not sit underneath the notch, the home indicator, or a rounded screen corner. The viewport unit sets the overall size, and the safe-area inset adds the extra padding a physical cutout needs.

svh vs dvh: how to choose

SituationUseWhy
Hero section, landing pagesvhContent always visible on load, no layout jump
Full-screen fixed modal / overlaydvhShould fill exactly what the user sees at all times
Sticky sidebarsvhStable height avoids scroll-triggered reflow
App shell (navigation + content area)dvhApp-style layout should track the visible area
Max-height caplvhUseful as a ceiling value combined with min-height: svh

Using dvh, svh and lvh in Tailwind CSS

Tailwind CSS added built-in support for the new viewport units in version 3.4, and the same classes carry over unchanged in Tailwind v4. There is no plugin or config needed, the utilities are part of the default theme.

Tailwind classCSS output
h-dvhheight: 100dvh
h-svhheight: 100svh
h-lvhheight: 100lvh
min-h-dvhmin-height: 100dvh
min-h-svhmin-height: 100svh
min-h-lvhmin-height: 100lvh
max-h-dvhmax-height: 100dvh
max-h-svhmax-height: 100svh
max-h-lvhmax-height: 100lvh
<div class="min-h-dvh flex items-center justify-center">
  <!-- hero content -->
</div>

Tailwind does not generate a vh fallback for you automatically. For older-browser support, add a plain CSS fallback rule to your stylesheet using the same pattern shown earlier, or accept that the handful of users on very old browsers simply get the browser's default sizing instead.

Troubleshooting common dvh and svh problems

The new units are well supported, but a few edge cases still catch people out in real projects.

dvh not updating smoothly on iOS Safari

Some versions of iOS Safari have shown inconsistent dvh behaviour: the value briefly matching svh instead of tracking the live viewport, or updating with a noticeable delay when the address bar shows or hides. If you see this on a target device, switch that element to svh. It gives up the "always exact" behaviour of dvh but avoids the visible glitch entirely.

Content hidden behind a Chromium bottom toolbar

Chrome for Android and other Chromium-based browsers let users move the address bar to the bottom of the screen, and can hide it entirely while scrolling. svh already accounts for this since it always reflects the smallest possible viewport, but a layout only tested against vh on a resized desktop window can still get content clipped by that bottom bar on a real device. Always test full-height layouts on an actual phone before shipping.

dvh and the on-screen keyboard

When a virtual keyboard opens on a form, different mobile browsers resize the viewport differently, and dvh does not behave consistently enough to rely on for keyboard-aware layouts. For anything that needs to react to the keyboard opening, such as keeping an input field visible above it, use the window.visualViewport JavaScript API instead of a CSS viewport unit.

Nothing happens without a fallback

If a browser does not recognise a unit like svh, it does not fall back to treating it as vh, it drops the entire declaration and behaves as if that line was never written. This is why the fallback pattern always writes the vh value on its own line first: browsers that do not understand the new units simply keep that first line, and everything still works. For very old browsers, or for equivalent dynamic resizing without waiting on native support, the older technique is a small JavaScript snippet that sets a CSS custom property on resize:

// legacy fallback for browsers without dvh support
function setVH() {
  const vh = window.innerHeight * 0.01;
  document.documentElement.style.setProperty('--vh', `${vh}px`);
}
setVH();
window.addEventListener('resize', setVH);
.hero {
  height: calc(var(--vh) * 100); /* legacy JS fallback */
  height: 100svh;              /* modern browsers use this */
}

In 2026 this JS workaround is rarely necessary since native support is already above 90%, but it is worth knowing if you need to support an older embedded WebView or kiosk browser that has not updated.

The relationship with the old vh, vw and vmin

The classic vh unit behaves like lvh in practice on most mobile browsers. It was defined to match the initial viewport before any scrolling, which on modern mobile browsers means the browser UI is included in the measurement. You can think of vh as an approximation of lvh on mobile, and the reason the problem exists at all.

On desktop browsers there is no difference between vh, svh, dvh, and lvh because the viewport height does not change as you interact with the page. All four units resolve to the same value on desktop. The new units only behave differently on devices where browser UI can appear and disappear.

Desktop vs mobile summary: On desktop, svh = dvh = lvh = vh. On mobile, svh is the visible height with browser UI showing, lvh is the full screen with browser UI hidden, and dvh fluctuates between the two. You only need to care about which unit to use when targeting mobile layouts.

Further reading

For the formal definitions and live browser support data, these are the primary sources worth bookmarking:

Frequently asked questions

What is the difference between dvh, svh and lvh?

svh (small viewport height) is the viewport height with the browser UI fully visible, the smallest it will ever be. lvh (large viewport height) is the viewport height with the browser UI fully hidden, the largest it will ever be. dvh (dynamic viewport height) updates in real time as the browser UI shows or hides. For most full-height layouts, svh is the safest choice because it never causes overflow.

Why does 100vh not work on mobile?

Mobile browsers include their address bar and tab bar in the viewport height measurement. So 100vh equals the full screen including that browser UI, which means content gets cut off at the bottom on load. When the user scrolls, the browser UI retracts and 100vh becomes taller than the visible area, causing a layout jump. The svh, dvh and lvh units were introduced to solve this.

Should I use dvh or svh?

Use svh when content must not overflow and a small gap at the bottom is acceptable. This is the right choice for most hero sections. Use dvh when you need the layout to always fill the exact visible screen and have confirmed on a real device that the constant resizing does not cause visual jank.

What browsers support dvh, svh and lvh?

Chrome 108, Safari 15.4, Firefox 101, and Edge 108. Global support is above 90% as of 2026. Write a vh fallback before the svh or dvh declaration to handle older browsers.

Does the same problem exist for vw with dvw, svw and lvw?

Yes, the same family exists for width: svw, lvw, and dvw. However the problem is much less common in practice because mobile browser UI does not resize the viewport horizontally, only vertically. The height variants are what you will encounter and need in real projects.

Does dvh work reliably in Safari on iPhone?

Mostly, but not perfectly. iOS Safari has supported dvh since version 15.4, and it works correctly in most layouts. Some versions have shown minor inconsistencies where the value does not update as smoothly as expected while the address bar animates. If you notice a glitch on a real iPhone, switch that element to svh for stable, predictable sizing instead.

What happens in browsers that do not support dvh at all?

Nothing, and that is the risk. A browser that does not recognise dvh drops the whole declaration rather than falling back to vh. Always write a vh value on the line above your svh or dvh declaration so unsupported browsers still get a usable height instead of no value at all.

Is there a Tailwind CSS class for dvh, svh and lvh?

Yes. Tailwind CSS has included h-dvh, h-svh, h-lvh, and their min-h-* and max-h-* equivalents since version 3.4, with no plugin required. These classes work the same way in Tailwind v4.

Convert px to rem and rem to px instantly

Stop doing unit conversion manually. Our free converter handles both directions with custom base font size and bulk conversion support.

Open PX to REM Converter →