Responsive Design
Mobile-First: Start Small, Add Breakpoints
Responsive design means your site works well on every screen size. The mobile-first approach starts with the smallest screen and adds complexity as space allows. This is not just a convention -- it produces better CSS.
Starting small forces you to prioritize content. You figure out what matters before adding layout for larger screens.
/* Base styles: mobile (no media query) */
.card-grid {
display: grid;
gap: 16px;
}
/* Tablet and up */
@media (min-width: 768px) {
.card-grid {
grid-template-columns: repeat(2, 1fr);
}
}
/* Desktop and up */
@media (min-width: 1024px) {
.card-grid {
grid-template-columns: repeat(3, 1fr);
}
}
Media Queries: Use min-width
min-width is the mobile-first approach: styles apply above the breakpoint. max-width is desktop-first: styles apply below the breakpoint. Use min-width.
/* Mobile-first with min-width (recommended) */
.nav {
flex-direction: column; /* Stacked on mobile */
}
@media (min-width: 768px) {
.nav {
flex-direction: row; /* Horizontal on tablet+ */
}
}
The min-width approach means your base styles are the simplest. Each breakpoint adds layout rather than removing it. Desktop-first with max-width requires overriding desktop styles for mobile, producing more overrides.
Common Breakpoints
/* Small phones */
/* No query needed -- this is the default */
/* Large phones / Small tablets */
@media (min-width: 640px) { }
/* Tablets */
@media (min-width: 768px) { }
/* Laptops */
@media (min-width: 1024px) { }
/* Desktops */
@media (min-width: 1280px) { }
/* Large desktops */
@media (min-width: 1536px) { }
These are guidelines, not rules. Set breakpoints where your content breaks, not where specific devices start. If a layout looks awkward at 900px, add a breakpoint at 900px.
Fluid Typography with clamp()
Instead of changing font sizes at breakpoints, use clamp() for smooth scaling:
h1 {
font-size: clamp(2rem, 1.5rem + 2.5vw, 4rem);
/* Minimum 2rem, scales with viewport, maximum 4rem */
}
h2 {
font-size: clamp(1.5rem, 1.2rem + 1.5vw, 2.5rem);
}
body {
font-size: clamp(1rem, 0.9rem + 0.4vw, 1.125rem);
}
The formula clamp(min, preferred, max) works as:
- Below a certain viewport width: uses
min - At the target viewport width: uses
preferred(which includes avwcomponent for scaling) - Above a certain viewport width: caps at
max
No media queries. The text scales fluidly.
Responsive Images
Images are often the largest assets on a page. Serving a 2000px image to a 375px phone wastes bandwidth.
srcset: Resolution Switching
Provide multiple sizes and let the browser choose:
<img
src="photo-800.jpg"
srcset="
photo-400.jpg 400w,
photo-800.jpg 800w,
photo-1200.jpg 1200w,
photo-1600.jpg 1600w
"
sizes="
(min-width: 1024px) 50vw,
(min-width: 640px) 75vw,
100vw
"
alt="Mountain landscape at sunset"
>
srcsetlists available image files and their intrinsic widthssizestells the browser how wide the image will be displayed at different viewport widths- The browser combines these to pick the optimal file (considering pixel density too)
The picture Element: Art Direction
Use <picture> when you want different images (not just sizes) at different breakpoints:
<picture>
<!-- Wide landscape crop for desktop -->
<source
media="(min-width: 1024px)"
srcset="hero-wide-1200.jpg 1200w, hero-wide-1800.jpg 1800w"
sizes="100vw"
>
<!-- Taller crop for mobile -->
<source
media="(min-width: 640px)"
srcset="hero-medium-800.jpg 800w"
sizes="100vw"
>
<!-- Square crop for small screens -->
<img
src="hero-square-400.jpg"
alt="Product showcase"
width="400"
height="400"
>
</picture>
Format Switching
Serve modern formats with fallbacks:
<picture>
<source type="image/avif" srcset="photo.avif">
<source type="image/webp" srcset="photo.webp">
<img src="photo.jpg" alt="Description">
</picture>
AVIF and WebP are significantly smaller than JPEG at comparable quality.
Preventing Layout Shift
Always include width and height attributes on images:
<img src="photo.jpg" alt="Description" width="800" height="600">
img {
max-width: 100%;
height: auto;
}
The browser uses the width and height to calculate the aspect ratio before the image loads, preventing layout shift. Combined with max-width: 100% and height: auto, the image scales responsively.
Container Queries for Component-Level Responsiveness
Media queries respond to the viewport. Container queries respond to the component's container. This is the right tool for reusable components.
.card-wrapper {
container-type: inline-size;
}
/* Card stacks vertically in narrow containers */
.card {
display: flex;
flex-direction: column;
}
/* Card switches to horizontal layout in wider containers */
@container (min-width: 500px) {
.card {
flex-direction: row;
gap: 24px;
}
.card-image {
flex: 0 0 200px;
}
}
The same card component works in a sidebar (narrow, stacked) and the main content area (wide, horizontal) without any modifications. This is component-driven responsive design.
Viewport Units
| Unit | Meaning |
|---|---|
vw |
1% of viewport width |
vh |
1% of viewport height |
dvh |
1% of dynamic viewport height |
svh |
1% of small viewport height |
lvh |
1% of large viewport height |
vmin |
1% of the smaller viewport dimension |
vmax |
1% of the larger viewport dimension |
The vh Problem on Mobile
On mobile browsers, 100vh does not equal the visible screen. The URL bar and bottom navigation take up space, making 100vh too tall.
/* Broken on mobile: content extends behind browser chrome */
.hero {
height: 100vh;
}
/* Fixed: uses the dynamic viewport height */
.hero {
height: 100dvh;
}
dvhchanges as the browser chrome shows/hides (the address bar)svhis the smallest possible viewport (browser chrome fully visible)lvhis the largest possible viewport (browser chrome hidden)
Use dvh for full-screen sections to avoid the mobile vh bug.
Testing Responsiveness
Chrome DevTools Device Mode
- Open DevTools (F12 or Cmd+Shift+I)
- Click the device toggle icon (or Cmd+Shift+M)
- Select a device preset or enter custom dimensions
- Test at various widths by dragging the viewport handle
What to Check
- Does content reflow correctly at every width?
- Is text readable without horizontal scrolling?
- Are touch targets at least 44x44 pixels on mobile?
- Do images scale and not overflow?
- Is the navigation usable on small screens?
- Does the layout work in both portrait and landscape?
Test at key widths: 320px (smallest phones), 375px (standard phone), 768px (tablet), 1024px (laptop), 1280px (desktop), 1920px (large desktop).
Common Pitfalls
- Using
max-widthmedia queries (desktop-first): This approach requires overriding desktop styles for mobile.min-width(mobile-first) adds layout progressively and produces cleaner CSS. - Breakpoints based on devices: Devices change constantly. Set breakpoints where your content needs them, not at arbitrary device widths.
- Not testing between breakpoints: Your layout might look great at 375px and 768px but break at 500px. Resize continuously, not just to preset widths.
- Using
vhon mobile:100vhis taller than the visible screen on mobile browsers. Usedvhfor full-screen layouts. - Serving desktop images to mobile: A 2000px image on a 375px screen wastes bandwidth and slows load times. Use
srcsetandsizes. - Forgetting
widthandheighton images: Without them, the browser cannot reserve space before the image loads, causing layout shift. - Over-relying on media queries: Many responsive patterns are achievable with
clamp(),min(),auto-fit, and container queries -- no media queries needed. - Not using
min()for container width:width: min(90%, 1200px)is cleaner than amax-widthwith separatewidth: 90%.
Key Takeaways
- Mobile-first means starting with styles for small screens and adding layout with
min-widthmedia queries - Set breakpoints where your content breaks, not at specific device widths
clamp()enables fluid typography and spacing without media queries- Use
srcsetandsizeson images to serve appropriate sizes; use<picture>for art direction - Container queries make components responsive to their container, not the viewport
- Use
dvhinstead ofvhfor full-screen sections on mobile repeat(auto-fit, minmax(250px, 1fr))creates responsive grids without media queries- Always include
widthandheighton images to prevent layout shift - Test responsiveness by continuously resizing, not just at preset device widths