Module 9

Responsive Web Design

Mobile-First • Media Queries • Breakpoints • Responsive Images • Viewport Units

๐Ÿ“ฑ Why Responsive Design Is Non-Negotiable

Over 60% of web traffic comes from mobile devices. Users browse on phones, tablets, laptops, desktops, and even TVs. A website that only looks good at one screen size is a broken website. Responsive Web Design (RWD) is the practice of building websites that adapt fluidly to any screen size and orientation.

The term was coined by Ethan Marcotte in 2010. His three pillars remain the foundation: fluid grids (percentage-based layouts, which you learned with Flexbox and Grid), flexible images, and media queries. In this module, we go beyond the basics to master the complete responsive toolkit.

๐Ÿ’ก
The Water Analogy: Think of your content like water. Pour water into a glass, it becomes the glass. Pour it into a bottle, it becomes the bottle. Your design should flow and take the shape of whatever container (device) it's viewed in โ€” that's responsive design.
โš ๏ธ
The viewport meta tag is required Without <meta name="viewport" content="width=device-width, initial-scale=1.0"> in your <head>, mobile browsers will render your page at a desktop width (~980px) and zoom out. This single tag tells the browser to use the device's real width. Every responsive page needs it.

๐ŸŽฏ By the end of this module, you will:

  • Understand and apply the mobile-first approach
  • Write media queries with min-width, max-width, and combined conditions
  • Design a breakpoint strategy based on content, not devices
  • Use viewport units (vw, vh, dvh, svh, lvh) effectively
  • Serve responsive images with srcset, sizes, and <picture>
  • Create fluid typography with clamp()
  • Use container queries for component-level responsiveness
  • Test and debug responsive layouts with browser DevTools

๐Ÿ“ฒ The Mobile-First Approach

Mobile-first means writing your base CSS for the smallest screens first, then using min-width media queries to add complexity for larger screens. This is the opposite of the "desktop-first" approach that was common before smartphones.

๐Ÿ’ก
Why mobile-first? Mobile-first forces you to prioritize content. On a small screen, there's no room for decoration โ€” only what matters. It's easier to add complexity for large screens than to remove it for small ones. Performance is also better: mobile devices download only the CSS they need, without loading desktop-heavy styles they'll override anyway.
CSS
/* === BASE: Mobile styles (no media query) === */
.container {
  padding: 16px;
}

.grid {
  display: grid;
  grid-template-columns: 1fr;  /* Single column on mobile */
  gap: 16px;
}

/* === TABLET: 768px and up === */
@media (min-width: 768px) {
  .container {
    padding: 32px;
  }
  .grid {
    grid-template-columns: repeat(2, 1fr);  /* Two columns */
    gap: 24px;
  }
}

/* === DESKTOP: 1024px and up === */
@media (min-width: 1024px) {
  .container {
    max-width: 1200px;
    margin: 0 auto;
    padding: 48px;
  }
  .grid {
    grid-template-columns: repeat(3, 1fr);  /* Three columns */
    gap: 32px;
  }
}

Notice the pattern: base styles have no media query โ€” they apply everywhere. Then @media (min-width: ...) progressively enhances the layout. Each breakpoint adds complexity, never removes it.

๐Ÿšซ
Desktop-first anti-pattern Avoid writing wide layouts first and then using max-width queries to undo them for mobile. This creates more CSS, forces mobile devices to download and parse unnecessary rules, and requires overriding properties instead of setting them. Always start from the simplest layout.

๐Ÿ” Media Queries In Depth

A media query applies CSS rules only when certain conditions are true โ€” typically the viewport width, but also height, orientation, color scheme preference, reduced motion, and more.

CSS
/* Syntax: @media [type] (feature: value) { ... } */

/* Width-based (most common) */
@media (min-width: 768px) { /* styles for โ‰ฅ768px */ }
@media (max-width: 767px) { /* styles for โ‰ค767px */ }

/* Range syntax (modern, level 4) */
@media (768px <= width < 1024px) { /* tablet only */ }

/* Combining conditions with AND */
@media (min-width: 768px) and (orientation: landscape) { }

/* Combining conditions with OR (comma) */
@media (max-width: 600px), (orientation: portrait) { }

/* NOT */
@media not (min-width: 768px) { /* same as max-width: 767px */ }

Useful Media Features

CSS
/* Dark mode preference */
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0f172a;
    --text: #e2e8f0;
  }
}

/* Reduced motion preference */
@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

/* Hover capability (touch vs. mouse) */
@media (hover: hover) {
  .card:hover {
    transform: translateY(-4px);
  }
}

/* High-DPI screens (Retina) */
@media (min-resolution: 2dppx) {
  .logo { background-image: url('logo@2x.png'); }
}
๐Ÿ’ก
Modern range syntax (Media Queries Level 4) Instead of @media (min-width: 768px) and (max-width: 1023px), you can write @media (768px <= width < 1024px). It's cleaner and avoids the off-by-one math. Supported in all modern browsers since 2023.

๐Ÿ“ Breakpoint Strategy

A breakpoint is the viewport width at which you change the layout. The key insight: set breakpoints where your design breaks, not where a specific device starts. There are thousands of screen sizes โ€” you cannot target them all.

CSS
/* A practical breakpoint system */
/* Base: 0 โ€“ 599px   โ†’ Mobile phones */
/* sm:   600px+      โ†’ Large phones, small tablets */
/* md:   768px+      โ†’ Tablets */
/* lg:   1024px+     โ†’ Small desktops, landscape tablets */
/* xl:   1280px+     โ†’ Desktops */
/* 2xl:  1536px+     โ†’ Large desktops */

:root {
  --bp-sm:  600px;
  --bp-md:  768px;
  --bp-lg:  1024px;
  --bp-xl:  1280px;
  --bp-2xl: 1536px;
}

/* Note: CSS custom properties can't be used in media queries.
   These variables are for reference and use in JS. */

@media (min-width: 600px)  { /* sm  */ }
@media (min-width: 768px)  { /* md  */ }
@media (min-width: 1024px) { /* lg  */ }
@media (min-width: 1280px) { /* xl  */ }
@media (min-width: 1536px) { /* 2xl */ }
โš ๏ธ
Content-driven breakpoints Rather than memorizing device sizes, resize your browser until the layout looks bad โ€” that's where to add a breakpoint. A sidebar that overlaps at 820px? Add a breakpoint there. Text lines too long at 900px? Break there. This approach is future-proof because it doesn't depend on specific devices.

Order matters

In mobile-first CSS, media queries must be in ascending order of min-width. If a 1024px query comes before the 768px query, the 768px rules will override the 1024px ones at large widths (because of the CSS cascade). Always order from smallest to largest.

๐Ÿ“ Viewport Units

Viewport units let you size elements relative to the browser window. They are essential for full-screen sections, responsive spacing, and fluid sizing.

vw
1% of the viewport width. 100vw = full viewport width. Useful for full-bleed elements.
vh
1% of the viewport height. 100vh = full viewport height. Common for hero sections.
vmin / vmax
vmin = smaller of vw/vh. vmax = larger of vw/vh. Great for elements that should scale consistently regardless of orientation.
dvh / svh / lvh
New viewport units (2022+). dvh (dynamic) accounts for mobile browser chrome (URL bar, etc.) that appears/disappears. svh = smallest possible viewport. lvh = largest possible viewport. Use dvh for full-screen mobile layouts.
CSS
/* Full-screen hero section */
.hero {
  min-height: 100dvh;  /* dynamic viewport height */
  display: grid;
  place-items: center;
  padding: 2rem;
}

/* Full-bleed section that breaks out of a container */
.full-bleed {
  width: 100vw;
  margin-left: calc(-50vw + 50%);
}

/* Responsive spacing using viewport width */
.section {
  padding-block: clamp(2rem, 5vw, 6rem);
}
๐Ÿšซ
The 100vh trap on mobile On mobile browsers, 100vh does not account for the URL bar and bottom navigation chrome. A 100vh element will overflow the actual visible area. Use 100dvh instead, which dynamically adjusts as browser chrome appears and disappears. Fallback: min-height: 100vh; min-height: 100dvh;

๐Ÿ–ผ๏ธ Responsive Images

Images are often the heaviest part of a webpage. Serving a 2000px-wide image to a 375px-wide phone wastes bandwidth and slows down the page. Responsive images let the browser pick the best image for each situation.

Fluid Images (the basics)

CSS
/* Every image should have this */
img {
  max-width: 100%;
  height: auto;
  display: block;
}

max-width: 100% ensures images never overflow their container. height: auto preserves the aspect ratio. This is the bare minimum for responsive images.

srcset & sizes โ€” resolution switching

The srcset attribute tells the browser about multiple image files at different widths. The browser picks the best one based on viewport width and device pixel ratio:

HTML
<img
  src="photo-800.jpg"
  srcset="photo-400.jpg 400w,
         photo-800.jpg 800w,
         photo-1200.jpg 1200w,
         photo-1600.jpg 1600w"
  sizes="(max-width: 600px) 100vw,
        (max-width: 1024px) 50vw,
        33vw"
  alt="A beautiful landscape"
  width="1600"
  height="900"
/>

srcset lists the available files and their intrinsic widths (in w units). sizes tells the browser how wide the image will be at each breakpoint. The browser combines this info with the device's pixel ratio to pick the optimal file. On a phone at 375px with 2x display, it might pick the 800w image (375 ร— 2 = 750).

The <picture> element โ€” art direction

When you need different crops or aspect ratios for different screen sizes (not just different resolutions), use <picture>:

HTML
<picture>
  <!-- Wide crop for desktops -->
  <source
    media="(min-width: 1024px)"
    srcset="hero-wide.jpg"
  />

  <!-- Square crop for tablets -->
  <source
    media="(min-width: 600px)"
    srcset="hero-square.jpg"
  />

  <!-- Tall crop for phones (default) -->
  <img
    src="hero-tall.jpg"
    alt="Hero image"
    width="600"
    height="800"
  />
</picture>
๐Ÿ’ก
Modern image formats Use <picture> to serve modern formats like WebP and AVIF with fallbacks: <source type="image/avif" srcset="photo.avif"> <source type="image/webp" srcset="photo.webp"> <img src="photo.jpg">. AVIF is ~50% smaller than JPEG; WebP is ~30% smaller.
โš ๏ธ
Always set width and height Always include width and height attributes on <img>. The browser uses these to calculate the aspect ratio before the image loads, preventing layout shifts (CLS). Combine with max-width: 100%; height: auto; in CSS.

๐Ÿ”ค Responsive Typography

Font sizes that look great on desktop can be too large on mobile or too small when zoomed out. Fluid typography scales smoothly between a minimum and maximum size based on the viewport width.

clamp() โ€” the modern approach

CSS
/* clamp(minimum, preferred, maximum) */

h1 {
  /* At least 2rem, ideally 5vw, but no more than 4rem */
  font-size: clamp(2rem, 5vw, 4rem);
}

h2 {
  font-size: clamp(1.5rem, 3.5vw, 2.5rem);
}

p {
  /* Body text: subtle scaling */
  font-size: clamp(1rem, 1rem + 0.25vw, 1.125rem);
  line-height: 1.6;
}

/* Responsive spacing too */
.section {
  padding: clamp(2rem, 5vw, 6rem);
}

clamp(min, preferred, max) returns the preferred value as long as it's between min and max. On a narrow screen, the preferred value (which is vw-based) might fall below the minimum, so the minimum kicks in. On a wide screen, the max caps it. One line, zero media queries.

โš ๏ธ
Accessibility: don't use only vw for font sizes Setting font-size: 5vw without a clamp() or rem base breaks browser zoom. Users who zoom in to 200% won't see any change because viewport width stays the same. Always combine vw with a rem base: font-size: clamp(1rem, 1rem + 2vw, 3rem).

Responsive line length

For readability, lines of body text should be 45โ€“75 characters wide. Use max-width in ch units (the width of the "0" character) to enforce this:

CSS
.prose {
  max-width: 65ch;  /* ~65 characters per line */
  margin-inline: auto;
}

๐Ÿ“ฆ Container Queries

Media queries respond to the viewport size. But what if a component lives in a sidebar on one page and in a wide main area on another? It needs to respond to its container's size, not the viewport. That's what container queries do.

CSS
/* Step 1: Define a containment context */
.card-wrapper {
  container-type: inline-size;
  container-name: card;
}

/* Step 2: Query the container's width */
@container card (min-width: 400px) {
  .card {
    display: grid;
    grid-template-columns: 150px 1fr;
    gap: 16px;
  }
}

@container card (min-width: 600px) {
  .card {
    grid-template-columns: 200px 1fr;
  }
  .card h3 {
    font-size: 1.5rem;
  }
}

container-type: inline-size enables size queries on the container. Then @container works like @media, but measures the container width instead of the viewport. This is a game-changer for reusable components.

๐Ÿ’ก
Container query units Container queries also introduce new units: cqw (1% of container width), cqh (1% of container height), cqi (inline size), cqb (block size). Use them to size children relative to their container: font-size: 3cqw;
โš ๏ธ
Browser support Container queries are supported in all major browsers since February 2023 (Chrome 105+, Firefox 110+, Safari 16+). They're now safe for production use in most projects.

๐Ÿ—๏ธ Responsive Design Patterns

Let's put it all together with real-world responsive patterns you'll use on every project.

Pattern 1: Responsive Navigation

The most common responsive pattern: a horizontal navbar for desktop that collapses into a hamburger menu on mobile.

CSS
/* Mobile: stacked links, hidden by default */
.nav-links {
  display: none;
  flex-direction: column;
  gap: 8px;
  padding: 16px;
}

.nav-links.open {
  display: flex;
}

.hamburger {
  display: block;
  cursor: pointer;
}

/* Desktop: horizontal nav, no hamburger */
@media (min-width: 768px) {
  .nav-links {
    display: flex;
    flex-direction: row;
    gap: 24px;
    padding: 0;
  }
  .hamburger {
    display: none;
  }
}

Pattern 2: Collapsible Sidebar

A sidebar that sits alongside the content on desktop, but stacks above the content (or hides behind a toggle) on mobile:

CSS
/* Mobile: single column, sidebar on top */
.layout {
  display: grid;
  grid-template-columns: 1fr;
  gap: 24px;
}

/* Desktop: sidebar + main */
@media (min-width: 768px) {
  .layout {
    grid-template-columns: 260px 1fr;
  }
}

Pattern 3: Responsive Card Grid

Auto-reflowing cards โ€” no media queries needed. You already learned this in the Grid module:

CSS
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 24px;
}

Pattern 4: Responsive Table โ†’ Stacked Cards

Tables are notoriously hard on mobile. A common pattern converts table rows into stacked cards:

CSS
/* Mobile: stack table rows vertically */
@media (max-width: 600px) {
  table, thead, tbody, th, td, tr {
    display: block;
  }
  thead {
    position: absolute;
    left: -9999px;  /* Hide header visually */
  }
  tr {
    margin-bottom: 16px;
    border: 1px solid #e2e8f0;
    border-radius: 8px;
    padding: 12px;
  }
  td {
    display: flex;
    justify-content: space-between;
    padding: 8px 0;
    border-bottom: 1px solid #f1f5f9;
  }
  td::before {
    content: attr(data-label);
    font-weight: 600;
  }
}

๐Ÿงช Testing & Debugging Responsive Layouts

Building responsive is only half the job โ€” you need to test it. Here are the essential tools and techniques.

Browser DevTools

Every modern browser has a device toolbar (responsive design mode). In Chrome/Edge: press F12 โ†’ click the device icon (or Ctrl+Shift+M). In Firefox: Ctrl+Shift+M. In Safari: enable Developer menu โ†’ Responsive Design Mode.

Resize freely
Drag the viewport edges to any width. Watch at which points your design breaks โ€” those are your breakpoints.
Device presets
Test with common devices (iPhone, iPad, Pixel). But don't rely only on presets โ€” real-world screens come in all sizes.
Network throttling
Simulate slow 3G connections to see how your images and fonts load. Performance matters especially on mobile.
Touch simulation
DevTools can simulate touch events. Test that hover effects degrade gracefully and tap targets are at least 44ร—44px.

Responsive QA Checklist

๐Ÿ“
No horizontal scroll

At any viewport width, the page should never scroll horizontally. Check for elements with fixed widths or 100vw that don't account for scrollbar width.

๐Ÿ”ค
Readable text

Body text at 16px minimum. Line length 45โ€“75 characters. Test at 200% browser zoom โ€” text should still be readable.

๐Ÿ‘†
Touch targets

Buttons and links need at least 44ร—44px tap area. Small targets frustrate mobile users.

๐Ÿ–ผ๏ธ
Image loading

Verify that responsive images serve appropriate sizes. A phone shouldn't download a 4MB desktop image.

โœ๏ธ Practice: Responsive Web Design

Test your understanding of mobile-first, media queries, responsive images, fluid typography, and container queries.

Exercise 1: Mobile-First Grid

Write mobile-first CSS for a .cards container: (1) single column on mobile, (2) two columns from 600px, (3) three columns from 1024px. Use 20px gap at all sizes.

Reveal Solution

.cards { display: grid; grid-template-columns: 1fr; gap: 20px; }

@media (min-width: 600px) { .cards { grid-template-columns: repeat(2, 1fr); } }

@media (min-width: 1024px) { .cards { grid-template-columns: repeat(3, 1fr); } }

๐Ÿ’ก Mobile-first: base styles use 1fr (single column). Each min-width query adds columns. The queries must be in ascending order.

Exercise 2: Responsive Image

Write an <img> tag that serves four image sizes (400, 800, 1200, 1600px wide). On mobile the image should be 100vw wide, on tablet 50vw, and on desktop 33vw.

Reveal Solution

<img src="photo-800.jpg" srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1200.jpg 1200w, photo-1600.jpg 1600w" sizes="(max-width: 600px) 100vw, (max-width: 1024px) 50vw, 33vw" alt="Description" width="1600" height="900">

๐Ÿ’ก sizes tells the browser the image's display width at each viewport. The browser combines this with srcset widths and the device pixel ratio to pick the best file.

Exercise 3: Fluid Typography

Write a clamp() rule for an h1 that: (1) is at least 1.75rem, (2) scales with the viewport at 4vw, (3) caps at 3.5rem. Why should you avoid font-size: 4vw alone?

Reveal Solution

h1 { font-size: clamp(1.75rem, 4vw, 3.5rem); }

๐Ÿ’ก Using only font-size: 4vw breaks browser zoom accessibility โ€” zooming in doesn't change viewport width, so the font stays the same size. clamp() with a rem minimum ensures the font is always at least 1.75rem even when viewed at small widths, and respects zoom.

Exercise 4: Container Query

Create a container query setup: (1) a .widget-area parent that is a size container, (2) when the container is at least 500px wide, change .widget from a stacked layout to a horizontal grid with image (150px) + content (1fr).

Reveal Solution

.widget-area { container-type: inline-size; container-name: widgets; }

@container widgets (min-width: 500px) { .widget { display: grid; grid-template-columns: 150px 1fr; gap: 16px; } }

๐Ÿ’ก container-type: inline-size enables queries based on the container width. @container then works like @media but measures the parent, not the viewport. This makes the widget truly reusable โ€” it adapts to wherever it's placed.

๐Ÿ“‹ Module Summary

๐Ÿ“ฒ
Mobile-First

Write base CSS for small screens. Use min-width media queries to add complexity. Start simple, enhance progressively.

๐Ÿ”
Media Queries

@media (min-width: ...) for width. Also: prefers-color-scheme, prefers-reduced-motion, hover. Modern range syntax: (768px <= width < 1024px).

๐Ÿ“
Breakpoints

Set breakpoints where your content breaks, not where devices start. Common: 600, 768, 1024, 1280. Order ascending in mobile-first.

๐Ÿ“
Viewport Units

vw, vh for viewport-relative sizing. Use dvh on mobile instead of vh. Combine with clamp() for fluid layouts.

๐Ÿ–ผ๏ธ
Responsive Images

max-width: 100% always. srcset + sizes for resolution switching. <picture> for art direction and modern formats.

๐Ÿ”ค
Fluid Typography

clamp(min, preferred, max) for smooth scaling. Always include a rem base for zoom accessibility. 45โ€“75ch line length.

๐Ÿ“ฆ
Container Queries

container-type: inline-size + @container for component-level responsiveness. Respond to parent size, not viewport.

Next Module โ†’

Module 10: Transitions & Animations

CSS transitions, keyframe animations, transforms, performance best practices, and creating delightful micro-interactions.

โ†’