Chapter 17: CSS Media Queries, Responsive Web Design & Viewport
Responsive Web Design (RWD) ensures web applications adapt seamlessly across smartphones, tablets, laptops, dynamic notch displays, and ultra-wide desktop monitors. Mastering viewport management, mobile-first CSS architecture, modern Media Query Range syntax (width >= 768px), mobile safe area insets (env()), fluid typography (clamp()), and next-generation Container Queries (@container) creates truly device-agnostic user experiences.
17.1 Viewport Meta Tag & Mobile Safe Area Insets (env())
The HTML viewport meta tag tells mobile browser engines to render the layout at the device's actual physical screen width rather than simulating a 980px desktop view. Setting viewport-fit=cover extends full-bleed backgrounds into device camera notches and home indicator curves, managed using env() variables:
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">padding-top: max(16px, env(safe-area-inset-top));padding-bottom: max(16px, env(safe-area-inset-bottom));
| Environmental Variable | Description | Typical Use Case |
|---|---|---|
env(safe-area-inset-top) |
Top inset distance from edge to clear camera notch or Dynamic Island. | Fixed headers, top app bars, sticky banners. |
env(safe-area-inset-bottom) |
Bottom inset distance from edge to clear home gesture indicator bar. | Fixed bottom navigation bars, floating action buttons (FAB). |
env(safe-area-inset-left) / right |
Left and right side insets for landscape orientation on curved screens. | Full-width hero section padding in landscape mode. |
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<style>
*, *::before, *::after { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; background: #0f172a; color: #f8fafc; margin: 0; }
/* Fixed Bottom Nav using env() Safe Area Inset */
.mobile-nav {
position: fixed; bottom: 0; left: 0; right: 0;
background: #1e293b; border-top: 1px solid #334155;
padding-top: 12px;
/* Protects against overlapping Home Indicator bar on mobile devices */
padding-bottom: max(12px, env(safe-area-inset-bottom));
display: flex; justify-content: space-around;
font-size: 12px; font-weight: bold; color: #38bdf8;
}
</style>
</head>
<body>
<div style="padding:24px;">
<h3 style="color:#38bdf8;margin-top:0;">Mobile Safe Area Insets Demo</h3>
<p style="font-size:12px;color:#94a3b8;">The bottom bar below automatically uses env(safe-area-inset-bottom) to prevent home bar collisions!</p>
</div>
<div class="mobile-nav">
<span>🏠 Home</span>
<span>🔍 Search</span>
<span>⚙️ Settings</span>
</div>
</body>
</html>
17.2 Modern Media Query Range Syntax (width >= 768px)
CSS Media Queries Level 4 introduced Range Media Query Syntax using mathematical comparison operators (>=, <=, >, <). This eliminates verbose, confusing min-width / max-width syntax and avoids off-by-one pixel overlap bugs:
| Target Viewport Condition | Legacy Syntax (Media Queries Level 3) | Modern Range Syntax (Media Queries Level 4) |
|---|---|---|
| Minimum width 768px (Tablets+) | @media (min-width: 768px) |
@media (width >= 768px) |
| Maximum width 640px (Mobile only) | @media (max-width: 640px) |
@media (width <= 640px) |
| Exact range between 768px and 1024px | @media (min-width: 768px) and (max-width: 1024px) |
@media (768px <= width <= 1024px) |
<!DOCTYPE html>
<html>
<head>
<style>
*, *::before, *::after { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; background: #0f172a; color: #f8fafc; padding: 24px; margin: 0; }
h3 { margin-top: 0; color: #818cf8; }
/* Mobile Base (< 768px) */
.range-card {
background: rgba(239, 68, 68, 0.2); border: 2px solid #ef4444; color: #f87171;
padding: 16px; border-radius: 10px; font-size: 14px; font-weight: bold; text-align: center;
transition: all 0.3s ease;
}
.range-card::after { content: " (Mobile Viewport < 768px)"; }
/* Modern Range Query: 768px <= width <= 1024px */
@media (768px <= width <= 1024px) {
.range-card {
background: rgba(245, 158, 11, 0.2); border-color: #f59e0b; color: #fbbf24;
}
.range-card::after { content: " (Tablet Range: 768px <= width <= 1024px)"; }
}
/* Modern Range Query: width >= 1025px */
@media (width >= 1025px) {
.range-card {
background: rgba(16, 185, 129, 0.2); border-color: #10b981; color: #34d399;
}
.range-card::after { content: " (Desktop Viewport: width >= 1025px)"; }
}
</style>
</head>
<body>
<h3>Modern Range Media Query Detector</h3>
<p style="font-size:12px;color:#64748b;">Resize viewport window to trigger Range Query intervals:</p>
<div class="range-card">
Active State:
</div>
</body>
</html>
17.3 Fluid Typography & Dynamic Layout Spacing (clamp())
Instead of creating harsh step-based typography jumps across media query breakpoints, clamp(MIN, VAL, MAX) dynamically interpolates font sizes and spacing continuously based on viewport width:
font-size: clamp(1.25rem, 0.8rem + 1.5vw, 2.75rem);Floor = 1.25rem (20px mobile min) | Ideal Slope = 0.8rem + 1.5vw | Ceiling = 2.75rem (44px desktop max)
<!DOCTYPE html>
<html>
<head>
<style>
*, *::before, *::after { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; background: #0f172a; color: #f8fafc; padding: 24px; margin: 0; }
/* Fluid Heading using clamp() */
.fluid-title {
font-size: clamp(1.25rem, 0.8rem + 2vw, 2.5rem);
color: #38bdf8; font-weight: 800; margin: 0 0 8px;
}
/* Fluid Container Padding */
.fluid-box {
background: #1e293b; border: 2px solid #38bdf8; border-radius: 12px;
padding: clamp(12px, 3vw, 32px);
}
</style>
</head>
<body>
<div class="fluid-box">
<h1 class="fluid-title">Truly Fluid Heading Scaling</h1>
<p style="font-size:13px;color:#94a3b8;margin:0;">Both title font-size and card padding smoothly scale with viewport size without any discrete media query jumps!</p>
</div>
</body>
</html>
17.4 Container Queries (@container, cqw, cqh & Container Units)
While Media Queries test the outer screen viewport width, Container Queries (@container) enable component-driven design by querying the size of an element's direct parent container. Container Query length units (cqw, cqh, cqi, cqb) provide relative sizing based on container dimensions:
| Container Unit | Definition & Basis | Viewport Equivalent |
|---|---|---|
cqw |
1% of query container's width | vw (Viewport Width) |
cqh |
1% of query container's height | vh (Viewport Height) |
cqi |
1% of query container's inline size (width in horizontal writing mode) | vi (Viewport Inline) |
cqb |
1% of query container's block size (height in horizontal writing mode) | vb (Viewport Block) |
cqmin / cqmax |
Smaller / larger value of cqi or cqb |
vmin / vmax |
<!DOCTYPE html>
<html>
<head>
<style>
*, *::before, *::after { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; background: #0f172a; color: #f8fafc; padding: 24px; margin: 0; display: flex; flex-direction: column; gap: 20px; }
h3 { margin-top: 0; color: #10b981; }
/* Establish Container Context */
.widget-container {
container-type: inline-size;
container-name: product-widget;
background: #1e293b; border: 2px dashed #10b981; border-radius: 12px;
padding: 16px; resize: horizontal; overflow: auto;
width: 100%; max-width: 480px;
}
/* Base Component Layout */
.widget-body { display: flex; flex-direction: column; gap: 10px; }
.widget-title { font-size: clamp(14px, 4cqw, 22px); color: #34d399; font-weight: bold; }
/* Container Query: Reflow when container width >= 380px */
@container product-widget (min-width: 380px) {
.widget-body {
flex-direction: row;
align-items: center;
justify-content: space-between;
}
}
</style>
</head>
<body>
<h3>@container Query & cqw Unit Demo</h3>
<p style="font-size:12px;color:#64748b;">Drag bottom-right handle to resize container. Component reflows and scales text via cqw units:</p>
<div class="widget-container">
<div class="widget-body">
<div class="widget-title">Container-Aware Product Card</div>
<button style="background:#10b981;color:#fff;border:none;padding:8px 16px;border-radius:6px;font-weight:bold;cursor:pointer;">Buy Now</button>
</div>
</div>
</body>
</html>
17.5 Interactive Sandbox — Range Queries, clamp() & Container Queries
Test Range Media Queries, fluid clamp() typography, and @container queries live in the interactive playground below: