Back to CSS
CSS
easy
junior

How do you approach responsive design with a mobile first mindset, breakpoints, and container queries?

Mobile-first: write base styles for narrow viewports, layer min-width media queries to upgrade. Use rem/em + clamp() for fluid type. Container queries (@container) let components respond to their slot, not the viewport.

6 min read·~12 min to think through

Responsive design isn't "make it look fine on phone" — it's a discipline of designing for an unknown viewport, then layering breakpoints. The senior signal is mobile-first thinking, fluid units, and knowing container queries replaced 80% of media-query gymnastics.

Mobile-first means base styles target the narrowest viewport. You then layer min-width media queries to upgrade for larger screens. Why mobile-first?

  • Mobile networks/devices are slower → smaller default CSS.
  • Mobile layouts are usually single-column — the simpler, default flow.
  • Larger-screen styles override only what's different.
  • The mental model matches how you build: start simple, add complexity as space allows.
css
/* Base — phones */
.card { padding: 12px; font-size: 14px; }

/* Tablet+ */
@media (min-width: 640px) { .card { padding: 16px; font-size: 16px; } }

/* Desktop+ */
@media (min-width: 1024px) { .card { padding: 24px; max-width: 800px; } }

Avoid max-width queries unless you genuinely need "this style only on phone" — they cascade backwards and pile up.

Standard breakpoints (Tailwind's, which most teams adopt):

  • sm: 640px (large phones / small tablets)
  • md: 768px (tablets)
  • lg: 1024px (laptops)
  • xl: 1280px
  • 2xl: 1536px

Don't invent your own without reason. Pick a system; consumers and design tools assume them.

Fluid units beat hard breakpoints.

  • rem — sizes scale with the user's root font-size (a11y win).
  • %, vw, vh, dvh — relative to viewport. Note: use 100dvh (dynamic viewport height) instead of 100vh to handle iOS Safari's address bar correctly.
  • clamp(min, preferred, max) — fluid sizing that auto-clamps at extremes:
css
font-size: clamp(1rem, 0.5rem + 1.5vw, 1.5rem);
/* Scales with viewport but never below 16px or above 24px. */

This is how modern design systems (Tailwind v4, design tokens like Radix/shadcn) avoid stair-stepping at every breakpoint.

Container queries (@container) — game-changer.

Media queries respond to the viewport. Container queries respond to a parent element's size. A card placed in a wide sidebar can render differently than the same card on a narrow page — without changing the card's component code.

css
.card-grid {
  container-type: inline-size;
}

@container (min-width: 600px) {
  .card { display: grid; grid-template-columns: 1fr 2fr; }
}

Browser support is solid (Safari 16+, Chrome 105+). Use them whenever the question is "how should this component look at this width" — which is almost always.

Common patterns.

  • Hidden on small vs hidden on large — hidden md:block pattern.
  • Stack → rowflex-col md:flex-row.
  • Hamburger — show on mobile, full nav on desktop. Use <button aria-expanded> for the hamburger.
  • Touch-first hit targets — 44px minimum tap area; padded buttons, larger nav links.

Images. Already covered separately — srcset/sizes for fluid responsive images, <picture> for art direction (different crops per viewport).

Testing.

  • Real device > Chrome DevTools "responsive" mode. Especially iOS Safari.
  • Browserslist + visual regression (Playwright/Chromatic) at multiple viewport widths.
  • prefers-reduced-motion, prefers-color-scheme, prefers-contrast are responsive-design concerns too — design tokens swap on these.

Common mistakes.

  • Desktop-first with max-width queries piling up — overrides cascade poorly.
  • Hard-coded breakpoint values everywhere instead of design tokens.
  • Using px for font-size and ignoring user's root font-size preference (a11y).
  • 100vh containers on iOS Safari — leaves a gap when the address bar collapses; use 100dvh.
  • Hiding content with display: none on mobile but expecting it to count for SEO/a11y — it doesn't.

Code

css
/* Base: stacked, full-width */
.product-card { display: flex; flex-direction: column; gap: 12px; }
.product-card img { width: 100%; }

/* When the card's container is wide enough, go side-by-side */
.product-grid { container-type: inline-size; }

@container (min-width: 480px) {
  .product-card { flex-direction: row; align-items: center; }
  .product-card img { width: 40%; }
}
Mobile-first + container query for a card
css
:root {
  --step-0: clamp(1rem, 0.95rem + 0.25vw, 1.125rem);
  --step-1: clamp(1.25rem, 1rem + 1vw, 1.75rem);
  --step-2: clamp(1.75rem, 1.25rem + 2vw, 3rem);
}
h1 { font-size: var(--step-2); }
p  { font-size: var(--step-0); }
Fluid typography with clamp

Follow-up questions

  • Why mobile-first instead of desktop-first?
  • When do you reach for container queries over media queries?
  • What's the difference between vh and dvh?
  • How does clamp() avoid stair-stepping at breakpoints?

Common mistakes

  • Desktop-first → max-width queries everywhere → cascade chaos.
  • Using vh for full-screen layouts on iOS Safari — leaves gaps.
  • Hard-coded px for font-size — ignores user preference.
  • Putting media queries inline per component instead of design tokens.

Performance considerations

  • Use container-type: inline-size only — full size triggers more layout work.
  • clamp() is computed on every layout — fine, but don't go nuts on critical-path elements.

Edge cases

  • Container queries don't work on display: contents elements.
  • 100dvh vs 100svh vs 100lvh — small/dynamic/large viewport variants for mobile.
  • Print media (@media print) — separate ruleset entirely.

Real-world examples

  • Tailwind responsive utilities, shadcn/ui card components using container queries, design systems with clamp-based typography (Stripe, Linear).

Senior engineer discussion

Senior signal: mobile-first instinct, container queries over media queries for components, fluid units (clamp + dvh), and design-token-driven breakpoints.

Related questions