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: 100vhforces the element to be exactly viewport tall. -
min-height: 100vhmakes 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.
.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),
svhis often a safe choice. -
If you want a section that tracks the viewport as the browser UI changes,
dvhis usually the “modern full-screen app” choice. -
If you intentionally want “largest possible viewport” sizing (or you’re matching older behavior),
lvhworks, but it can reintroduce the classic100vhissues 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:
-
Set a fallback first (like
min-height: 100vh;). -
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
heightwhen you should usemin-height? -
Did you accidentally set
overflow: hiddenonbodyor a tall section? -
On mobile, is the issue only happening when the address bar is visible?
If yes, try
svhordvh. -
Are you relying on
vhfor 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
vhfor viewport-based sizing (desktop-friendly). -
Prefer
min-heightoverheightwhen content can grow. -
On mobile: consider
svh(small),lvh(large),dvh(dynamic). -
Fallback pattern: write
vhfirst, then override withdvh. - vh + vw is great for layout blocks, but clamp viewport-based typography.
