Responsive Design
Media queries, mobile-first design, rem/em/%/vw/vh units, clamp(), and responsive images with srcset.
What responsive design means
Responsive design means a single page adapts its layout to whatever screen it's viewed on — a phone, a tablet, a widescreen monitor — rather than shipping a separate "mobile site." The two pillars that make it work are media queries (conditionally applying CSS based on viewport characteristics) and relative units (sizing things proportionally instead of in fixed pixels).
Media queries
A media query applies a block of CSS only when a condition about the viewport is true:
.container {
width: 100%;
padding: 1rem;
}
@media (min-width: 768px) {
.container {
width: 750px;
margin: 0 auto;
}
}
@media (min-width: 1024px) {
.container {
width: 960px;
}
}
Common conditions:
@media (min-width: 768px) { /* screen is at least 768px wide */ }
@media (max-width: 767px) { /* screen is at most 767px wide */ }
@media (orientation: landscape) { /* wider than tall */ }
@media (prefers-color-scheme: dark) { /* user's OS is set to dark mode */ }
Mobile-first
Mobile-first means writing your base (unqualified) CSS for the smallest screen, then using min-width media queries to progressively add complexity as the viewport grows — rather than writing desktop CSS first and cramming it into small screens with max-width overrides.
/* Base styles — apply to all screens, mobile included */
.nav-links {
display: none; /* hidden on mobile, revealed by a hamburger toggle */
}
.hamburger {
display: block;
}
/* Reveal full nav once there's room for it */
@media (min-width: 768px) {
.nav-links {
display: flex;
gap: 1.5rem;
}
.hamburger {
display: none;
}
}
This approach tends to produce simpler, more maintainable CSS: mobile is the true baseline, and each breakpoint only adds enhancements rather than undoing desktop-first assumptions.
Relative units: rem, em, %, vw, vh
| Unit | Relative to |
|---|---|
px |
Absolute — a fixed pixel value, doesn't scale with anything |
% |
The parent element's corresponding dimension |
em |
The current element's own font-size (or the parent's, for properties other than font-size) |
rem |
The root element's (<html>) font-size — consistent no matter how deeply nested |
vw / vh |
1% of the viewport's width / height |
html {
font-size: 16px; /* the default in every browser, set explicitly here for clarity */
}
.card {
padding: 1rem; /* 16px, regardless of nesting */
font-size: 1.125rem; /* 18px */
}
.card .icon {
width: 1em; /* scales with *this element's* font-size */
height: 1em;
}
.hero {
height: 60vh; /* 60% of the viewport height, regardless of content */
}
rem is generally preferred over em for most sizing, because em compounds — a 1.2em inside another 1.2em inside another produces a multiplied, hard-to-predict result, while rem always resolves against the single root font-size no matter how deeply nested.
/* em compounding problem */
.parent { font-size: 1.2em; } /* 1.2 × 16px = 19.2px */
.parent .child { font-size: 1.2em; } /* 1.2 × 19.2px = 23px, not 19.2px */
clamp() — fluid sizing without media queries
clamp(min, preferred, max) picks a value that scales fluidly between a minimum and a maximum, driven by a "preferred" value that's usually viewport-relative:
h1 {
/* never smaller than 1.5rem, never larger than 3rem,
scales fluidly with viewport width in between */
font-size: clamp(1.5rem, 4vw + 1rem, 3rem);
}
.container {
width: clamp(300px, 90%, 1200px);
}
This often eliminates several font-size media query breakpoints entirely — the text scales smoothly across every viewport width instead of jumping in steps at specific breakpoints.
Responsive images
<img
src="photo-800.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1200.jpg 1200w"
sizes="(max-width: 600px) 100vw, 50vw"
alt="A mountain landscape"
>
srcset— lists the same image at multiple resolutions, each tagged with its intrinsic width (400wmeans 400 pixels wide).sizes— tells the browser how wide the image will actually be displayed at different viewport widths, so it can pick the smallestsrcsetcandidate that's still large enough — avoiding downloading a huge image for a small display slot.
CSS alone also helps images behave responsively by default:
img {
max-width: 100%;
height: auto; /* preserves aspect ratio as width shrinks */
}
Without max-width: 100%, an image simply overflows its container at its native pixel size on small screens — one of the most common responsive-design bugs.
Common mistakes
- Writing desktop CSS first and retrofitting mobile with
max-widthoverrides — usually produces more overrides and more specificity fights than starting mobile-first. - Using
pxfor font sizes throughout a site, which ignores the user's browser-level font-size preference (accessibility setting) sincepxnever scales with it, unlikerem. - Forgetting
max-width: 100%on images, letting large images overflow their container on narrow viewports. - Adding too many arbitrary breakpoints copied from a framework instead of choosing breakpoints based on where this specific layout actually starts to look broken.