CSS vh meaning

The vh unit means viewport height. Your viewport is the visible area of the browser (the “window” where the page is shown).

  • 1vh = 1% of the viewport height
  • 100vh = 100% of the viewport height (the whole visible height, in theory)

So if the viewport is 900px tall, 1vh is 9px, and 50vh is 450px.

You’ll most often use vh for “viewport-based sizing” like hero sections, full-screen panels, background blocks, and layouts that should scale with screen height.

.hero {
  height: 50vh;
}
  
.hero {
  height: 100vh;
}
  
.hero {
  height: 25vh;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo;
}

.hero {
  border-bottom: 3px solid #111;
  display: grid;
  place-items: center;
  padding: 24px;
  background: #f2f2f2;
}

.hero h2 {
  margin: 0;
  font-size: 20px;
}

.hero p {
  margin: 6px 0 0;
  opacity: 0.8;
}
  

Hero block

This block is sized with vh.

Try switching snippets: 25vh / 50vh / 100vh.

Note that the playgrounds aren't creating a "new viewport" fixed to the size of the preview. vh is still based on your actual viewport height.

vh is relative, not fixed

Unlike px, vh changes when the viewport changes. Resize the browser window, rotate your phone, open the on-screen keyboard… and vh-based sizes can change.

Height vs min-height (the “why is my content cut off?” chapter)

A super common beginner mistake is using height: 100vh on a section that contains content. If the content is taller than the viewport, it must overflow somehow.

  • height: 100vh forces the element to be exactly viewport tall.
  • min-height: 100vh makes it at least viewport tall, but it can grow if content needs more space.
.section {
  height: 100vh;
}
  
.section {
  min-height: 100vh;
}
  
.section {
  min-height: 100vh;
  padding: 32px;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo;
}

.section {
  border: 3px solid #111;
  background: #f2f2f2;
  padding: 16px;
}

.card {
  background: #fff;
  border: 2px solid #111;
  padding: 16px;
  max-width: 520px;
}

.card h3 {
  margin: 0 0 10px;
  font-size: 18px;
}

.card p {
  margin: 0 0 12px;
  line-height: 1.35;
}
  

Content that might be taller than the viewport

If you use height: 100vh, this section is forced to be exactly viewport tall. If content grows, it can overflow.

If you use min-height: 100vh, it starts at viewport height but can grow.

Add padding? That also affects how cramped things feel.

Extra: imagine this text is a real hero with buttons, nav, logos, and a cookie banner. Things get tall fast.

The overflow trap

If you combine height: 100vh with overflow: hidden (on the body or the section), you can accidentally hide content with no way to scroll to it. For full-screen sections that can scroll, prefer min-height and let the page scroll naturally.

CSS vh and vw (the power couple)

vh is based on viewport height. vw is based on viewport width.

  • 1vw = 1% of the viewport width
  • 1vh = 1% of the viewport height

They’re great together for responsive blocks that “feel” like they belong to the screen. (Just don’t go wild and make everything vw or you’ll invent a new accessibility problem.)

.panel {
  width: 70vw;
  height: 35vh;
}
  
.panel {
  width: 90vw;
  height: 50vh;
}
  
.panel {
  width: 60vw;
  height: 60vh;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo;
  padding: 24px;
  background: #fff;
}

.wrap {
  display: grid;
  gap: 16px;
  place-items: start;
}

.panel {
  border: 3px solid #111;
  background: #f2f2f2;
  display: grid;
  place-items: center;
  padding: 16px;
}

.panel p {
  margin: 0;
  max-width: 44ch;
  line-height: 1.35;
}
  

This panel uses vw for width and vh for height. Try switching snippets.

A quick warning about vw typography

People love doing font-size: 4vw. It looks cool… until it doesn’t. On very wide screens it becomes huge, and on very narrow screens it can become tiny. If you ever use viewport units for text, combine them with limits using clamp().

h2 {
  font-size: clamp(1.25rem, 3vw, 2.25rem);
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo;
  padding: 24px;
}

.card {
  border: 3px solid #111;
  background: #f2f2f2;
  padding: 20px;
  max-width: 680px;
}

p {
  margin: 10px 0 0;
  line-height: 1.35;
}
  

Responsive heading with clamp()

The middle value uses vw, but clamp() sets a minimum and maximum size. Resize your browser window to see it scale safely.

When vh “doesn’t work” (mobile browser UI is the villain)

On desktop, 100vh is usually straightforward. On mobile browsers, the viewport height can be tricky because the browser UI (address bar, toolbars) can expand and collapse while you scroll.

Result: a “full screen” section sized with height: 100vh might be a little too tall (or too short), causing annoying gaps, content cut-off, or a page that scrolls by a few pixels for no obvious reason.

Classic symptoms

  • “My hero is taller than the screen on iPhone.”
  • “I see a tiny scroll even though everything should fit.”
  • “When I scroll, the layout jumps.”

CSS vh vs dvh vs svh vs lvh (the modern viewport units)

To fix the “mobile browser UI changes viewport height” problem, CSS introduced new viewport units: svh, lvh, and dvh. They are described as viewport-percentage units for small, large, and dynamic viewport sizes.

What each unit means

  • svh (small viewport height): based on the smallest “safe” viewport height (when browser UI is most visible, i.e. the address bar and navigation bar are shown). This tends to prevent content from being hidden behind browser UI.
  • lvh (large viewport height): based on the largest viewport height (when browser UI is collapsed).
  • dvh (dynamic viewport height): updates dynamically as the UI expands/collapses (it tracks the real-time viewport).

One extra detail that can get people: vh is equivalent to lvh (the large viewport size). That’s one reason why 100vh can feel “too tall” on mobile when the address bar is visible.

If you want a deeper conceptual explanation of why these were introduced, Google’s web.dev has a great write-up on large/small/dynamic viewport units.

Compare vh, svh, lvh, dvh in one playground

You will need to visit this playground on a mobile browser to see the differences in action.

height unit:
.stage {
  height: 100vh;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo;
}

.stage {
  border: 3px solid #111;
  background: #333;
  display: grid;
  place-items: center;
  padding: 16px;
}

.box {
  width: min(390px, 88vw);
  border: 2px solid #111;
  background: #fff;
  padding: 16px;
}

.box h3 {
  margin: 0 0 10px;
  font-size: 18px;
}

.box p {
  margin: 0 0 10px;
  line-height: 1.35;
}
  

Viewport unit comparison

Switch between vh, svh, lvh, and dvh.

On some mobile browsers, you’ll notice 100vh (often like 100lvh) can feel taller than the “visible” area when UI is showing.

So which one should you use?

  • If you want a section that always fits inside the currently visible area (avoid UI overlap), svh is often a safe choice.
  • If you want a section that tracks the viewport as the browser UI changes, dvh is usually the “modern full-screen app” choice.
  • If you intentionally want “largest possible viewport” sizing (or you’re matching older behavior), lvh works, but it can reintroduce the classic 100vh issues on mobile.

Browser support changes over time, so check support tables before relying on dvh/svh/lvh for your audience. Always set a vh fallback if you need to support older browsers.

Fallbacks and progressive enhancement (aka “don’t break older browsers”)

If you want to use the modern units safely, a common CSS approach is:

  1. Set a fallback first (like min-height: 100vh;).
  2. Then override with the modern unit (like min-height: 100dvh;).

Browsers that don’t understand dvh will ignore it and keep vh. Browsers that do understand it will use the better value.

.hero {
  min-height: 100vh;
  min-height: 100dvh;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo;
}

.hero {
  border-bottom: 3px solid #111;
  background: #f2f2f2;
  padding: 24px;
  display: grid;
  place-items: center;
}

.hero p {
  margin: 0;
  max-width: 62ch;
  line-height: 1.35;
}
  

This uses a fallback-first approach: min-height: 100vh then min-height: 100dvh. Newer browsers use dvh, older browsers keep vh.

Debug checklist for vh issues

  • Are you using height when you should use min-height?
  • Did you accidentally set overflow: hidden on body or a tall section?
  • On mobile, is the issue only happening when the address bar is visible? If yes, try svh or dvh.
  • Are you relying on vh for something that should be content-based (like a text-heavy section)?
  • Have you checked browser support for the modern units for your audience?

vh cheat sheet

  • Use vh for viewport-based sizing (desktop-friendly).
  • Prefer min-height over height when content can grow.
  • On mobile: consider svh (small), lvh (large), dvh (dynamic).
  • Fallback pattern: write vh first, then override with dvh.
  • vh + vw is great for layout blocks, but clamp viewport-based typography.