CSS Viewport Units have evolved significantly with new units like svh (small viewport height), dvh (dynamic viewport height), and lvh (large viewport height). These units solve long-standing issues with mobile browser address bars and create more reliable responsive designs.
The Problem: Traditional Viewport Units
Before these new units, designers struggled with 100vh not actually representing the visible viewport:
/* Traditional - often shows unwanted scrollbars on mobile */
.hero {
height: 100vh;
overflow: hidden;
}
The issue: Mobile browsers have dynamic UI elements (address bar, navigation) that change the visible viewport area.
How Mobile Browsers Work
โโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Browser UI (address) โ <- Often hidden
โโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ Visible Content โ <- What users see
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Browser UI (nav) โ <- Often hidden
โโโโโโโโโโโโโโโโโโโโโโโโโโโ
When the user scrolls, the browser UI hides, but 100vh doesn’t adjust:
/* Problem: Content gets hidden behind browser UI */
.hero {
height: 100vh; /* Fixed - doesn't adapt */
background: linear-gradient(to bottom, blue, purple);
}
The Solution: Dynamic Viewport Units
CSS now provides multiple viewport units to handle different scenarios:
Viewport Units Comparison
/* Small Viewport Height - smallest dynamic viewport */
height: 100svh; /* Always fits within visible area */
/* Large Viewport Height - largest dynamic viewport */
height: 100lvh; /* Includes potentially hidden areas */
/* Dynamic Viewport Height - matches current visible area */
height: 100dvh; /* Adapts as UI shows/hides */
/* Traditional - problematic on mobile */
height: 100vh; /* Fixed, doesn't adapt */
Small Viewport Height (svh)
The svh unit represents the smallest viewport height when all browser UI is visible:
/* Guaranteed to be visible without scrolling */
.hero {
height: 100svh;
display: flex;
align-items: center;
justify-content: center;
}
When to Use svh
- Hero sections that should always be fully visible
- Full-screen modals and dialogs
- Landing pages
- Any element that shouldn’t scroll when first viewed
/* Full-screen modal - always visible */
.modal-overlay {
position: fixed;
inset: 0;
height: 100svh;
width: 100svw;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.5);
}
Large Viewport Height (lvh)
The lvh unit represents the largest viewport height when browser UI is hidden:
/* May have parts hidden initially */
.hero {
height: 100lvh;
}
When to Use lvh
- Background elements that can extend beyond initial view
- Scroll-triggered animations
- Elements designed to scroll into view
/* Background that extends fully */
.page-background {
height: 100lvh;
width: 100%;
position: fixed;
top: 0;
left: 0;
background: linear-gradient(180deg, #1a1a2e, #16213e);
z-index: -1;
}
Dynamic Viewport Height (dvh)
The dvh unit dynamically matches the current visible viewport as browser UI shows/hides:
/* Automatically adapts to UI state */
.hero {
height: 100dvh;
transition: height 0.3s ease;
}
With Scroll
/* Content that fills viewport while scrolling */
.fullscreen-section {
min-height: 100dvh;
display: flex;
flex-direction: column;
}
/* Works smoothly as address bar shows/hides */
.main-content {
flex: 1;
overflow-y: auto;
}
Viewport Width Units
Similar units exist for width:
/* Viewport Width */
width: 100vw; /* Traditional - may cause horizontal scroll */
/* Small Viewport Width */
width: 100svw; /* Smallest visible width */
/* Large Viewport Width */
width: 100lvw; /* Largest visible width */
/* Dynamic Viewport Width */
width: 100dvw; /* Dynamic width */
Practical Examples
Example 1: Mobile-First Hero Section
/* Always visible hero section */
.hero {
height: 100svh;
min-height: -webkit-fill-available;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
padding: 2rem;
box-sizing: border-box;
}
.hero h1 {
font-size: clamp(2rem, 5vw, 4rem);
margin: 0;
}
.hero p {
font-size: clamp(1rem, 2.5vw, 1.5rem);
max-width: 60ch;
}
/* Fallback for older browsers */
@supports not (height: 100svh) {
.hero {
min-height: 100vh;
min-height: 100dvh;
}
}
Example 2: Fixed Navigation
/* Header that accounts for dynamic viewport */
.header {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 60px;
background: white;
z-index: 100;
}
.main-content {
padding-top: 60px;
/* Ensure content isn't hidden */
min-height: calc(100svh - 60px);
}
Example 3: Scrollable Content Area
/* Layout with fixed header and scrollable content */
.app-layout {
display: grid;
grid-template-rows: auto 1fr;
height: 100dvh;
height: 100svh;
overflow: hidden;
}
.header {
background: #333;
color: white;
padding: 1rem;
}
.content {
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
.content-inner {
padding: 2rem;
}
Example 4: Split Screen Layout
/* Full-height split screen */
.split-layout {
display: grid;
grid-template-columns: 1fr;
height: 100dvh;
height: 100svh;
}
@media (min-width: 768px) {
.split-layout {
grid-template-columns: 1fr 1fr;
}
}
.panel {
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
}
Example 5: Card Grid with Viewport Sizing
/* Responsive card grid */
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1rem;
padding: 1rem;
height: calc(100dvh - 100px);
overflow-y: auto;
}
.card {
background: white;
border-radius: 8px;
padding: 1.5rem;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
Example 6: Full-Screen Video Background
/* Video that always fills visible area */
.video-container {
position: relative;
height: 100svh;
width: 100vw;
overflow: hidden;
}
.video-bg {
position: absolute;
top: 50%;
left: 50%;
min-width: 100%;
min-height: 100%;
transform: translate(-50%, -50%);
}
.video-overlay {
position: absolute;
inset: 0;
background: rgba(0,0,0,0.4);
display: flex;
align-items: center;
justify-content: center;
}
Viewport Units in CSS Functions
With calc()
/* Calculate heights with viewport units */
.header {
height: calc(60px + 2svh);
}
.content {
min-height: calc(100dvh - 60px - 40px);
}
With clamp()
/* Responsive sizing */
.hero-title {
font-size: clamp(2rem, 5dvh, 5rem);
}
.section {
min-height: clamp(300px, 50dvh, 600px);
}
With container queries
/* Works with container queries too */
.card-container {
container-type: inline-size;
}
.card {
height: 100cqh; /* Container query height */
}
Legacy Browser Support
Use @supports for progressive enhancement:
/* Modern browsers */
.hero {
height: 100svh;
}
/* Fallback for older browsers */
@supports not (height: 100svh) {
.hero {
height: 100vh;
min-height: -webkit-fill-available;
min-height: -moz-available;
}
}
JavaScript Fallback
// Set CSS custom property for older browsers
function setViewportHeight() {
const vh = window.innerHeight * 0.01;
document.documentElement.style.setProperty('--vh', `${vh}px`);
}
// Use in CSS: height: calc(100 * var(--vh));
setViewportHeight();
window.addEventListener('resize', setViewportHeight);
Browser Support
| Unit | Chrome | Firefox | Safari | Edge |
|---|---|---|---|---|
| svh | 108+ | 101+ | 15.4+ | 108+ |
| dvh | 108+ | 101+ | 15.4+ | 108+ |
| lvh | 108+ | 101+ | 15.4+ | 108+ |
/* Feature detection */
@supports (height: 100svh) {
.hero { height: 100svh; }
}
Common Patterns
Pattern 1: Mobile Full Screen
.fullscreen-mobile {
height: 100svh;
height: 100dvh;
min-height: -webkit-fill-available;
}
Pattern 2: Scroll Container
.scroll-container {
height: 100dvh;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
Pattern 3: Fixed Background
.fixed-background {
position: fixed;
top: 0;
left: 0;
width: 100vw;
width: 100svw;
height: 100vh;
height: 100dvh;
z-index: -1;
}
Pattern 4: Sticky Footer
.page-wrapper {
display: flex;
flex-direction: column;
min-height: 100dvh;
min-height: 100svh;
}
.content {
flex: 1;
}
.footer {
margin-top: auto;
}
Best Practices
1. Use svh for Initial View
/* For elements that should be visible immediately */
.hero, .modal, .overlay {
height: 100svh;
}
2. Use dvh for Scrollable Content
/* For scrollable containers */
.main-content {
height: 100dvh;
overflow: hidden;
}
.inner-scroll {
height: 100%;
overflow-y: auto;
}
3. Combine with Modern CSS
/* Works great with container queries */
.card-container {
container-type: inline-size;
}
.card {
height: 100cqh;
}
@container (max-width: 400px) {
.card {
height: 50svh;
}
}
4. Test on Real Devices
/* Always test on actual mobile devices */
.hero {
height: 100svh;
/* Test with and without this */
min-height: -webkit-fill-available;
}
Debugging Viewport Issues
Chrome DevTools
- Open DevTools (F12)
- Toggle device toolbar (Ctrl+Shift+M)
- Test with address bar shown/hidden
- Check computed styles for actual values
Common Issues
/* Issue: Horizontal scroll */
/* Solution: Use 100svw instead of 100vw */
body {
width: 100svw;
}
/* Issue: Content hidden behind fixed elements */
/* Solution: Account for header height */
.main-content {
min-height: calc(100svh - 60px);
}
/* Issue: Animation jank */
/* Solution: Use dvh for smooth transitions */
.hero {
height: 100dvh;
transition: height 0.3s ease;
}
External Resources
- MDN: Viewport units
- CSS Working Group: Viewport Units
- Can I Use: CSS Viewport Units
- web.dev: New viewport units
Summary
Modern CSS viewport units solve long-standing mobile layout issues:
- svh (Small Viewport Height): Use for elements that should always be visible
- lvh (Large Viewport Height): Use for backgrounds and elements that can extend
- dvh (Dynamic Viewport Height): Use for scrollable content areas
- svw/dvw/lvw: Same concepts applied to width
- Browser Support: Chrome 108+, Firefox 101+, Safari 15.4+, Edge 108+
Start using these units today to create more reliable, mobile-friendly layouts!
Comments