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:

Css
.container {
    width: 100%;
    padding: 1rem;
}

@media (min-width: 768px) {
    .container {
        width: 750px;
        margin: 0 auto;
    }
}

@media (min-width: 1024px) {
    .container {
        width: 960px;
    }
}

Common conditions:

Css
@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.

Css
/* 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
Css
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.

Css
/* 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:

Css
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

HTML
<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 (400w means 400 pixels wide).
  • sizes — tells the browser how wide the image will actually be displayed at different viewport widths, so it can pick the smallest srcset candidate that's still large enough — avoiding downloading a huge image for a small display slot.

CSS alone also helps images behave responsively by default:

Css
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-width overrides — usually produces more overrides and more specificity fights than starting mobile-first.
  • Using px for font sizes throughout a site, which ignores the user's browser-level font-size preference (accessibility setting) since px never scales with it, unlike rem.
  • 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.