Skip to main content

📱 Lesson 15: Responsive Design

Your site looks great on your laptop. But what about on a phone? A tablet? A 4K monitor? Responsive design means building pages that adapt to any screen size — rearranging, resizing, and reflowing so the content is always readable and usable. It's not optional; over half of all web traffic comes from mobile devices.

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Explain why responsive design matters and what "mobile-first" means
  • Use the viewport meta tag correctly
  • Write media queries to apply styles at different screen widths
  • Choose sensible breakpoints for your layouts
  • Use fluid units (%, vw, vh, rem, em) instead of fixed pixels
  • Make images responsive with max-width: 100%
  • Build a mobile-first navigation that adapts to larger screens
  • Use clamp() for fluid typography
  • Test responsive layouts using browser DevTools

Estimated Time: 60 minutes

Hands-on: You'll make your project site fully responsive — from phone to desktop.

📑 In This Lesson

Why Responsive Design Matters

People visit websites on phones, tablets, laptops, desktops, TVs, and even smartwatches. A fixed-width layout that looks perfect at 1200px will force horizontal scrolling on a phone and waste acres of space on a 4K monitor.

Responsive design solves this by making your layout adapt to the available space. The same HTML renders differently depending on the screen width — content reflows, sidebars stack, font sizes adjust, and images scale.

graph LR A["Phone
~375px"] --> B["Tablet
~768px"] --> C["Laptop
~1024px"] --> D["Desktop
~1440px+"] style A fill:#fef2f2,stroke:#ef4444,stroke-width:2px,color:#1e293b style B fill:#fffbeb,stroke:#f59e0b,stroke-width:2px,color:#1e293b style C fill:#f0fdf4,stroke:#22c55e,stroke-width:2px,color:#1e293b style D fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b

Three core principles make a site responsive:

  1. Fluid layouts — use percentages, fr, and auto-fit instead of fixed pixel widths
  2. Flexible media — images and videos scale to fit their containers
  3. Media queries — apply different CSS rules at different screen sizes

You've actually been using the first two already. The auto-fit + minmax() grid from the last lesson is inherently responsive. Now you'll learn how to handle cases that need explicit breakpoints.

The Viewport Meta Tag

This line in your <head> is essential for responsive design:

<meta name="viewport" content="width=device-width, initial-scale=1.0">

Without it, mobile browsers pretend the screen is ~980px wide and zoom out to fit, making everything tiny and unreadable. This meta tag tells the browser: "use the actual device width, don't zoom out."

If you've been using Emmet's ! shortcut, this tag is already in your HTML. If not, add it now — nothing else in this lesson works without it.

💡 What Each Part Means

  • width=device-width — set the viewport width to the actual device screen width
  • initial-scale=1.0 — start at 100% zoom (no zooming out)

Never add maximum-scale=1 or user-scalable=no — these prevent users from zooming in, which is an accessibility problem.

Media Queries

A media query is a CSS block that only applies when certain conditions are true — most commonly, when the screen is at least a certain width.

/* Base styles — apply to all screens */
.container {
    padding: 16px;
}

/* Only applies when the screen is 768px or wider */
@media (min-width: 768px) {
    .container {
        padding: 24px 32px;
    }
}

The styles inside @media (min-width: 768px) only kick in when the browser window is 768 pixels wide or more. On smaller screens, only the base styles apply.

Syntax

@media (condition) {
    /* CSS rules that only apply when the condition is true */
}

/* Width-based */
@media (min-width: 768px) { }    /* 768px and wider */
@media (max-width: 767px) { }    /* 767px and narrower */

/* Combining conditions */
@media (min-width: 768px) and (max-width: 1023px) { }  /* tablet only */

/* Orientation */
@media (orientation: portrait) { }
@media (orientation: landscape) { }

/* Prefer reduced motion (accessibility) */
@media (prefers-reduced-motion: reduce) {
    * { animation: none !important; transition: none !important; }
}

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

Where to Place Media Queries

Put media queries at the bottom of your stylesheet (or at least after the base styles they modify). The cascade means later rules override earlier ones at the same specificity — so your media query overrides need to come after the defaults.

/* ===========================
   Base styles (mobile)
   =========================== */
.grid { display: grid; grid-template-columns: 1fr; gap: 16px; }

/* ===========================
   Tablet and up
   =========================== */
@media (min-width: 768px) {
    .grid { grid-template-columns: 1fr 1fr; gap: 20px; }
}

/* ===========================
   Desktop and up
   =========================== */
@media (min-width: 1024px) {
    .grid { grid-template-columns: 1fr 1fr 1fr; gap: 24px; }
}

Choosing Breakpoints

Breakpoints are the screen widths where your layout changes. Don't chase specific devices — devices change every year. Instead, choose breakpoints where your design breaks.

That said, here are commonly used breakpoints that work well as starting points:

Common Breakpoints
Name min-width Typical Devices
Small (sm) 640px Large phones (landscape)
Medium (md) 768px Tablets (portrait)
Large (lg) 1024px Tablets (landscape), small laptops
Extra large (xl) 1280px Laptops, desktops
2XL 1536px Large desktops, external monitors

For most projects, two or three breakpoints are enough: one around 768px (tablet) and one around 1024px (desktop). Don't add breakpoints you don't need — every one adds complexity.

💡 The Right Way to Find Breakpoints

Open your site and slowly drag the browser window narrower. When something looks awkward — text too cramped, a sidebar too narrow to be useful, cards squished to unreadable widths — that's where you need a breakpoint. Let the content dictate the breakpoints, not the devices.

Mobile-First Design

"Mobile-first" means writing your base CSS for the smallest screen, then using min-width media queries to add complexity for larger screens. The alternative — writing desktop styles first and overriding with max-width queries — is called "desktop-first" and tends to produce more overrides and messier code.

graph TD A["Mobile-First Approach"] --> B["Base CSS
(single column, stacked, simple)"] B --> C["@media min-width: 768px
(two columns, sidebar appears)"] C --> D["@media min-width: 1024px
(three columns, larger fonts)"] style A fill:#f0fdf4,stroke:#22c55e,stroke-width:2px,color:#1e293b style B fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b style C fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b style D fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b

Why Mobile-First?

  • Less code — mobile layouts are simpler (single column, stacked elements), so fewer overrides are needed
  • Progressive enhancement — you start with the essentials and add features for bigger screens, rather than stripping things away
  • Performance — mobile devices download only the base CSS; desktop enhancements load but don't apply until the screen is wide enough
  • Forces prioritization — when you design for a small screen first, you decide what actually matters

Mobile-First in Practice

/* Mobile (base): single column */
.page-grid {
    display: grid;
    grid-template-columns: 1fr;
    grid-template-areas:
        "header"
        "main"
        "sidebar"
        "footer";
    gap: 0;
}

/* Tablet: sidebar appears beside main content */
@media (min-width: 768px) {
    .page-grid {
        grid-template-columns: 220px 1fr;
        grid-template-areas:
            "header  header"
            "sidebar main"
            "footer  footer";
    }
}

/* Desktop: wider sidebar, max-width container */
@media (min-width: 1024px) {
    .page-grid {
        grid-template-columns: 280px 1fr;
        max-width: 1200px;
        margin: 0 auto;
    }
}

Notice the pattern: the base styles are the simplest layout. Each min-width query adds more structure. You never need to "undo" anything.

Fluid Units — %, vw, vh, rem, em

Fixed pixel values create rigid layouts. Fluid units create layouts that scale naturally with the screen or the user's font settings.

CSS Units Comparison
Unit Relative To Best For Example
px Nothing (absolute) Borders, shadows, small fixed details border: 1px solid
% Parent element's size Widths, max-widths width: 80%
vw Viewport width (1vw = 1%) Full-width sections, hero images width: 100vw
vh Viewport height (1vh = 1%) Full-height sections, hero banners min-height: 100vh
rem Root font size (usually 16px) Font sizes, spacing, padding, margins font-size: 1.25rem
em Parent element's font size Component-relative spacing padding: 0.5em 1em

rem — The Go-To Unit

rem stands for "root em" and is relative to the root element's (<html>) font size, which defaults to 16px in every browser. This makes it predictable and consistent:

/* All of these are based on the root 16px */
h1 { font-size: 2rem; }      /* 32px */
h2 { font-size: 1.5rem; }    /* 24px */
p  { font-size: 1rem; }      /* 16px */
.small { font-size: 0.875rem; } /* 14px */

/* Spacing with rem scales if the user changes their browser font size */
.card { padding: 1.25rem 1.5rem; margin-bottom: 1.5rem; }

The big advantage: if a user changes their browser's default font size (for accessibility), everything sized in rem scales proportionally. Pixel-based sizes don't.

When to Use Which

  • rem — your default for font sizes, padding, margins, gaps. Consistent and accessible.
  • % — widths and max-widths (e.g., max-width: 90%)
  • vw/vh — full-screen sections (e.g., min-height: 100vh for a hero)
  • px — borders, box-shadows, and tiny details that shouldn't scale
  • em — spacing that should scale with the element's own font size (less common than rem)

Responsive Images

Images with fixed pixel widths break responsive layouts — they overflow their containers on small screens. The fix is simple:

img {
    max-width: 100%;
    height: auto;
}

max-width: 100% means "never be wider than your container." height: auto preserves the aspect ratio. These two rules should be in every project's CSS reset.

Object-Fit for Cropped Images

When images need to fill a specific space (like a gallery thumbnail or a card header), use object-fit:

.card-image {
    width: 100%;
    height: 200px;
    object-fit: cover;     /* fills the area, crops if needed */
}
object-fit Values
Value Behavior
cover Fills the entire area, cropping if needed (most common)
contain Fits inside the area, preserving aspect ratio (may leave empty space)
fill Stretches to fill (distorts the image — usually avoid)
none Original size, no scaling (may overflow)

The <picture> Element for Art Direction

Sometimes you want to serve entirely different images at different screen sizes — a wide landscape shot on desktop but a cropped portrait version on mobile. The <picture> element handles this:

<picture>
    <source media="(min-width: 768px)" srcset="hero-wide.jpg">
    <source media="(min-width: 480px)" srcset="hero-medium.jpg">
    <img src="hero-small.jpg" alt="Sunset over the ocean">
</picture>

The browser picks the first <source> that matches. The <img> is the fallback for browsers that don't support <picture> and for the smallest screens.

Fluid Typography with clamp()

Media queries for font sizes work but create jarring jumps. clamp() creates smoothly scaling typography that flows between a minimum and maximum size:

/* clamp(minimum, preferred, maximum) */
h1 {
    font-size: clamp(1.75rem, 4vw, 3rem);
}

p {
    font-size: clamp(1rem, 1.5vw, 1.125rem);
}

Here's how clamp(1.75rem, 4vw, 3rem) works:

  • The font is at least 1.75rem (28px) — it never goes smaller
  • The font prefers to be 4vw (4% of the viewport width) — it scales with the screen
  • The font is at most 3rem (48px) — it never goes larger

On a 375px phone, 4vw = 15px, which is below the minimum, so the font stays at 1.75rem. On a 1200px desktop, 4vw = 48px, which matches the maximum, so it caps at 3rem. In between, it scales smoothly.

graph LR A["Phone: 375px
clamp hits minimum
1.75rem (28px)"] --> B["Tablet: 768px
scales with 4vw
~30.7px"] --> C["Desktop: 1200px
clamp hits maximum
3rem (48px)"] style A fill:#fef2f2,stroke:#ef4444,stroke-width:2px,color:#1e293b style B fill:#fffbeb,stroke:#f59e0b,stroke-width:2px,color:#1e293b style C fill:#f0fdf4,stroke:#22c55e,stroke-width:2px,color:#1e293b

💡 clamp() Works for More Than Fonts

You can use clamp() for padding, margins, gaps — anything with a size value:

.container {
    padding: clamp(16px, 4vw, 48px);
    gap: clamp(16px, 2vw, 32px);
}

Responsive Navigation Pattern

Navigation is usually the first thing that breaks on small screens. A horizontal nav bar with five links doesn't fit on a 375px screen. The standard solution: show a "hamburger" menu button on mobile that toggles the nav links.

The CSS-Only Approach

/* Mobile: stack nav links vertically, hidden by default */
.nav-links {
    display: none;
    flex-direction: column;
    gap: 0;
}

/* When the menu is toggled open */
.nav-links.active {
    display: flex;
}

/* The hamburger button */
.mobile-menu-toggle {
    display: block;
    background: none;
    border: none;
    font-size: 1.5rem;
    cursor: pointer;
    color: white;
}

/* Desktop: show nav links, hide the hamburger */
@media (min-width: 768px) {
    .nav-links {
        display: flex;
        flex-direction: row;
        gap: 24px;
    }

    .mobile-menu-toggle {
        display: none;
    }
}

The JavaScript Toggle

A tiny script handles the toggle (you've seen this if your course project already has a mobile menu):

<script>
    const toggle = document.getElementById('mobile-menu-toggle');
    const navLinks = document.getElementById('nav-links');

    toggle.addEventListener('click', () => {
        const isOpen = navLinks.classList.toggle('active');
        toggle.setAttribute('aria-expanded', isOpen);
    });
</script>

This is a standard pattern used on the vast majority of websites. The aria-expanded attribute tells screen readers whether the menu is open or closed.

Testing with DevTools

Every modern browser has a responsive design testing mode. In Chrome and Edge:

  1. Press F12 to open DevTools
  2. Click the device toggle button (the phone/tablet icon) in the top-left of the DevTools toolbar — or press Ctrl+Shift+M
  3. The viewport now has drag handles. Resize it to any width, or pick a preset device (iPhone, iPad, Pixel, etc.)
  4. Watch your media queries activate and deactivate as you resize

What to Test For

  • Text readability — can you read body text without zooming? Are headings too large on mobile?
  • Horizontal overflow — slowly narrow the viewport. Does anything cause a horizontal scrollbar?
  • Touch targets — are buttons and links at least 44×44 pixels for finger tapping?
  • Navigation — does the nav collapse to a hamburger menu on mobile? Does it work?
  • Images — do they scale down without overflowing?
  • Tables — wide tables often break on mobile. Consider wrapping them in a scrollable <div>
/* Make tables horizontally scrollable on small screens */
.table-wrap {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}

💡 The Scrollbar Test

The easiest way to spot responsive problems: set the viewport to 375px wide (iPhone SE size) and check for a horizontal scrollbar. If one appears, something is too wide. Common culprits: images without max-width: 100%, fixed-width elements, <pre> code blocks, and wide tables.

Hands-on Exercise

🏋️ Exercise: Make Your Site Responsive

Objective: Apply mobile-first responsive design to your project site so it works on phones, tablets, and desktops.

Instructions:

  1. Add the global responsive image reset to your CSS:
    img { max-width: 100%; height: auto; }
  2. Convert your page layout to mobile-first:
    • Base styles: single-column grid (grid-template-columns: 1fr)
    • At 768px: add the sidebar column
  3. Make your navigation responsive:
    • Mobile: hamburger button visible, nav links hidden (shown on toggle)
    • Desktop (768px+): hamburger hidden, nav links displayed as a flex row
  4. Add fluid typography using clamp():
    • h1: clamp(1.75rem, 4vw, 2.5rem)
    • h2: clamp(1.375rem, 3vw, 1.75rem)
  5. Wrap any <table> in a <div class="table-wrap"> with overflow-x: auto
  6. Use DevTools responsive mode to test at 375px, 768px, and 1200px
  7. Check for horizontal scrollbars at every width
💡 Hint

Start with the base mobile styles — everything in a single column. Then add a @media (min-width: 768px) block at the bottom of your stylesheet that introduces the sidebar, switches the nav to horizontal, and increases padding. You only need one or two media queries for a site this size.

✅ Example Solution
/* ===========================
   Responsive Reset
   =========================== */
img {
    max-width: 100%;
    height: auto;
}

/* ===========================
   Fluid Typography
   =========================== */
h1 { font-size: clamp(1.75rem, 4vw, 2.5rem); }
h2 { font-size: clamp(1.375rem, 3vw, 1.75rem); }
h3 { font-size: clamp(1.125rem, 2.5vw, 1.375rem); }

/* ===========================
   Mobile Base Layout
   =========================== */
.page-grid {
    display: grid;
    grid-template-columns: 1fr;
    grid-template-areas:
        "header"
        "main"
        "sidebar"
        "footer";
    min-height: 100vh;
}

/* Navigation — mobile */
.site-nav {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 12px 16px;
    flex-wrap: wrap;
}

.nav-links {
    display: none;
    width: 100%;
    flex-direction: column;
    gap: 0;
}

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

.nav-links a {
    padding: 12px 16px;
    border-top: 1px solid rgba(255, 255, 255, 0.1);
}

.mobile-menu-toggle {
    display: block;
}

/* Container — mobile */
.container {
    padding: clamp(16px, 4vw, 48px);
}

/* Cards — mobile */
.card-row {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

/* Tables — scrollable on mobile */
.table-wrap {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}

/* ===========================
   Tablet and Up (768px)
   =========================== */
@media (min-width: 768px) {
    .page-grid {
        grid-template-columns: 220px 1fr;
        grid-template-areas:
            "header  header"
            "sidebar main"
            "footer  footer";
    }

    /* Navigation — desktop */
    .nav-links {
        display: flex;
        width: auto;
        flex-direction: row;
        gap: 24px;
    }

    .nav-links a {
        padding: 0;
        border-top: none;
    }

    .mobile-menu-toggle {
        display: none;
    }

    /* Cards — side by side */
    .card-row {
        flex-direction: row;
        flex-wrap: wrap;
        gap: 20px;
    }

    .card-row .card {
        flex: 1 1 250px;
    }
}

/* ===========================
   Desktop (1024px)
   =========================== */
@media (min-width: 1024px) {
    .page-grid {
        grid-template-columns: 280px 1fr;
        max-width: 1200px;
        margin: 0 auto;
    }
}

🎯 Quick Quiz

Question 1: What does "mobile-first" mean in responsive design?

Question 2: What does @media (min-width: 768px) { ... } mean?

Question 3: How do you make images responsive?

Question 4: What does clamp(1rem, 3vw, 2rem) do?

Question 5: What unit is best for font sizes in a responsive design?

Summary

🎉 Key Takeaways

  • Responsive design makes your site adapt to any screen size — it's essential, not optional
  • The viewport meta tag (width=device-width, initial-scale=1.0) is required for mobile to work correctly
  • Media queries apply CSS rules conditionally based on screen width: @media (min-width: 768px) { }
  • Mobile-first = base styles for small screens + min-width queries for larger screens. Cleaner code, fewer overrides.
  • Choose breakpoints where your design breaks, not at specific device widths. Two or three is usually enough.
  • Use fluid units: rem for fonts/spacing, % for widths, vw/vh for full-screen sections, px for borders
  • Make images responsive: img { max-width: 100%; height: auto; }
  • clamp(min, preferred, max) creates fluid typography that scales smoothly — no jumps
  • Use object-fit: cover for images that need to fill a specific area
  • Test with DevTools responsive mode (Ctrl+Shift+M) — check for horizontal scrollbars at every width

📁 Your Project So Far

my-website/
├── css/
│   └── style.css      ← now fully responsive with media queries
├── images/
│   └── sunset.jpg
├── index.html         ← mobile-first layout
├── about.html
├── recipe.html
└── contact.html

🎓 Module 3 Complete!

You've finished all of CSS Essentials. You now know how to style colors and fonts, control spacing with the box model, build one-dimensional layouts with Flexbox, create two-dimensional layouts with Grid, and make everything responsive. That's a complete CSS toolkit.

🚀 What's Next?

Time to put it all together. In Module 4: Building Your Website, you'll plan and build a multi-page website from scratch — homepage, content pages, and all the polish — using everything you've learned. First up: Lesson 16: Planning Your Site, where you'll sketch a sitemap, wireframe your pages, and prepare your content before writing a single line of code.