Skip to main content

What are vh, vw, vmin and vmax in CSS

vh, vw, vmin, and vmax are CSS viewport units where 1 unit equals 1% of a viewport dimension: vh is height, vw is width, vmin is the shorter side, and vmax is the longer side.

Theory

TL;DR

  • 100vh fills the screen top to bottom, 100vw left to right
  • vmin locks to the shorter viewport side, vmax to the longer one, both flip when the device rotates
  • Unlike px, all four units recalculate on every resize and orientation change
  • Use viewport units for layout containers and hero sections, not for text (use rem there)
  • Gotcha: 100vh on iOS Safari overshoots the visible area by ~80px because of the address bar

Quick example

css
.hero { height: 100vh; /* fills the full viewport height */ width: 100vw; /* fills the full viewport width */ } .card { width: 40vmin; /* 40% of the shorter side, stays proportional on rotate */ height: 40vmin; background: #4a90e2; }

On a 1200x800 desktop: 1vh = 8px, 1vw = 12px, 1vmin = 8px. Rotate a phone from portrait to landscape and vmin sticks to the shorter side automatically. No JavaScript needed.

Key difference from other units

Viewport units always calculate against the browser window, not a parent element. 50vw on a deeply nested element still means half the screen. Compare that to 50%, which means half of whatever contains the element. That distinction matters: use viewport units for full-bleed sections, not for components that need to scale relative to their container.

When to use

  • Full-screen hero sections: height: 100vh
  • Square elements that survive orientation changes: width: 40vmin; height: 40vmin
  • Modals that fit under the browser chrome: max-height: 90vh
  • Wide banners that stretch to use maximum horizontal space: vmax
  • Body text: use rem, not viewport units

Comparison table

UnitBased onPortrait 390x844Landscape 844x390Use for
vhViewport height1vh = 8.44px1vh = 3.9pxFull-height sections
vwViewport width1vw = 3.9px1vw = 8.44pxHorizontal spans
vminSmaller side1vmin = 3.9px1vmin = 3.9pxSquare elements, no overflow
vmaxLarger side1vmax = 8.44px1vmax = 8.44pxMax-stretch banners
pxFixedAlways 1pxAlways 1pxPrecise, non-responsive

How the browser calculates these

The browser measures its initial containing block (the viewport rectangle, scrollbars excluded), divides by 100, and multiplies by your number. This recalculates on every resize event and orientation change. On mobile, iOS Safari historically included the address bar area in its vh calculation, so 100vh was taller than the visible screen by roughly 80px. Chrome 108 and iOS 15.4 introduced dvh (dynamic viewport height) to fix this.

Common mistakes

Using height: 100vh for a full-screen mobile layout:

css
/* Wrong: iOS Safari clips ~80px of content under the address bar */ .app { height: 100vh; } /* Fix */ .app { height: 100vh; /* fallback for older browsers */ height: 100dvh; /* Chrome 108+, iOS 15.4+ - overrides vh if supported */ }

I have seen this break the bottom navigation on more than one production Next.js app. Two lines fix it completely.

Using width: 100vw for a full-width nav:

css
/* Wrong: includes scrollbar width (~15px), causes horizontal scroll */ nav { width: 100vw; } /* Fix */ nav { width: 100%; }

No fallback for older browsers:

css
.box { width: 30%; } /* fallback */ .box { width: 30vmin; } /* modern browsers override */

IE 9-11 had partial support for vh and vw but none for vmin and vmax. A percentage fallback covers it.

Real-world usage

  • Tailwind CSS: h-screen compiles to height: 100vh
  • Bootstrap 5: uses --bs-full-height: 100vh in offcanvas and modal components
  • shadcn/ui: fullscreen loaders and backdrops use height: 100vh
  • Three.js canvas: width: 100vw; height: 100vh for scene containers
  • Next.js landing pages: hero sections and full-screen modal backdrops

Follow-up questions

Q: What is the difference between 100vh and height: 100%?
A: 100vh always equals the viewport height. height: 100% requires every parent element up to <html> to have an explicit height set, otherwise the element collapses to zero.

Q: Why does 100vh fail on mobile Safari?
A: Safari included the address bar area in its vh calculation, making 100vh taller than the visible screen. Use 100dvh on iOS 15.4+ and Chrome 108+. For older mobile browsers, the JS workaround with a CSS custom property still works.

Q: When would you pick vmin over vw?
A: When the element needs to stay visible in both portrait and landscape. vmin always references the shorter side, so a 40vmin element stays fully on screen after rotation without overflow.

Q: What are dvh, svh, and lvh?
A: Newer units from CSS Viewport Units Level 4. dvh is dynamic (tracks the actual visible height as the address bar appears or hides). svh is the smallest viewport (bar fully visible). lvh is the largest (bar fully hidden). All major browsers support them from 2023 onward.

Examples

Full-screen hero section

css
.hero { width: 100vw; height: 100vh; /* baseline for all browsers */ height: 100dvh; /* dynamic height for modern mobile */ display: flex; align-items: center; justify-content: center; background: #1a1a2e; }

The standard pattern for landing page heroes. The second height declaration overrides the first in browsers that support dvh, so no separate media query is needed.

Responsive modal

css
.modal { width: clamp(300px, 80vw, 800px); height: clamp(200px, 70vh, 600px); max-width: 90vmin; margin: auto; background: white; border-radius: 8px; overflow: auto; }

On a 1920x1080 desktop this lands at 800x600px. On a portrait phone it scales down to about 310x480px. The 90vmin cap prevents overflow in either orientation, so the modal works the same in portrait and landscape without extra media queries.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?