Concept
Responsive design is the practice of building layouts that work across the full range of real device sizes, not just adapting between a handful of fixed breakpoints, but genuinely fluid where possible. The core techniques: the viewport meta tag (a prerequisite for anything else to work), mobile-first media queries, fluid units, and increasingly, intrinsic/container-based sizing that needs no breakpoints at all.
The viewport meta tag, nothing works without it
<meta name="viewport" content="width=device-width, initial-scale=1" />Without this, mobile browsers render the page at a fixed desktop-width viewport (typically 980px) and scale the whole thing down to fit the screen, every media query you write becomes meaningless, since the browser reports a fake desktop-sized viewport regardless of the actual device. This single line is the actual prerequisite for responsive CSS to function at all on mobile, and it's covered here rather than purely in HTML because it's so fundamental to how CSS media queries even receive correct data.
Mobile-first vs. desktop-first media queries
/* Mobile-first: base styles = mobile, media queries ADD complexity as space grows */
.card { display: block; }
@media (min-width: 768px) {
.card { display: flex; }
}/* Desktop-first: base styles = desktop, media queries REMOVE/override as space shrinks */
.card { display: flex; }
@media (max-width: 767px) {
.card { display: block; }
}Mobile-first (using min-width queries, building up) is the broadly recommended approach: mobile styles become the unconditional baseline (simplest, fewest overrides, and, not incidentally, mobile users on slower connections get exactly the CSS they need with no unused desktop-only rules to parse), and each breakpoint progressively enhances rather than overrides. Desktop-first tends to produce more !important-adjacent override fights as breakpoints accumulate, since later, narrower media queries have to actively undo desktop assumptions.
Common breakpoint conventions
/* Common device-size-informed breakpoints (not a spec, just widely-used conventions) */
@media (min-width: 640px) { /* sm: large phones */ }
@media (min-width: 768px) { /* md: tablets */ }
@media (min-width: 1024px) { /* lg: small laptops */ }
@media (min-width: 1280px) { /* xl: desktops */ }The specific pixel values matter far less than the principle: breakpoints should be driven by where your content starts to look wrong, not copied from a framework's defaults or a specific device's screen width. Resize an actual browser window slowly and note exactly where the layout breaks, that's where a breakpoint belongs, not at an arbitrary "iPhone width" number that will be obsolete by next year's device lineup anyway.
Fluid units over fixed pixels
/* Fixed, same visual size regardless of user's browser font-size setting */
h1 { font-size: 32px; }
/* Fluid, scales with the user's root font-size (respects browser zoom/accessibility settings) */
h1 { font-size: 2rem; }
/* Fully fluid, scales smoothly with viewport width between two bounds, no breakpoint jump */
h1 { font-size: clamp(1.5rem, 4vw + 1rem, 3rem); }clamp(min, preferred, max) is the modern approach to fluid typography, the middle value (often a vw-based calculation) scales continuously with viewport width, while min/max bound it so text never gets illegibly small or absurdly large. This replaces the older pattern of setting a different fixed font-size at every breakpoint, which produces visible "jumps" in text size rather than smooth scaling.
rem (relative to the root <html> font-size) rather than px for typography is also an accessibility consideration: users who increase their browser's default font size (a common low-vision accommodation) get proportionally larger text throughout the site only if sizes are specified in relative units, px values ignore that user preference entirely.
Responsive images
<img
src="photo-800.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1200.jpg 1200w"
sizes="(min-width: 768px) 50vw, 100vw"
alt="..."
/>srcset + sizes lets the browser choose the most appropriately-sized image file for the current viewport and device pixel ratio, critical for real-world performance, since serving a 1200px-wide image to every device (including phones displaying it at 300px) wastes substantial bandwidth. The <picture> element extends this further for art direction (genuinely different crops/images at different breakpoints, not just different resolutions of the same image).
Testing viewport range, not just specific devices
Chrome DevTools' device toolbar (Cmd+Shift+M) lets you drag the viewport to any width, not just preset device sizes, dragging slowly across the full range from 320px to 2560px catches breakpoint issues that testing only "iPhone SE, iPad, Desktop" presets would miss, since real users exist at every width in between, especially on resizable desktop browser windows.
Common Mistakes
1. Missing the viewport meta tag entirely
Without it, every subsequent responsive effort is functionally inert on real mobile devices, even though it may look correct in a desktop browser's resized window (which doesn't reproduce the fake-desktop-viewport-then-scale-down behavior mobile browsers apply without the meta tag).
2. Designing/testing only at specific device widths (375px, 768px, 1440px) instead of the full range
/* Looks fine at 375px and 1440px, but breaks somewhere in between, nobody checked */Real users resize desktop browser windows to arbitrary widths and use a huge range of actual device sizes beyond the handful in a Figma file. Test by dragging the viewport continuously, not just jumping between fixed presets.
3. Fixed px font sizes ignoring user font-size preferences
Covered above, using px for typography silently breaks browser zoom/OS-level text-size accessibility settings for users who rely on them.
4. Breakpoints copied from a framework or a specific device, not driven by actual content
Bootstrap's or Tailwind's default breakpoints are reasonable starting conventions, not a guarantee your specific content/design won't break somewhere else, always verify against your actual layout, don't assume the defaults are automatically correct for your content.
5. Desktop-first CSS producing override wars
Starting from desktop styles and un-doing them at each narrower breakpoint tends to accumulate overrides that fight each other as more breakpoints are added over a project's lifetime, mobile-first's "add complexity as space grows" model scales better as a codebase matures.
6. Serving full-resolution images to every device regardless of actual display size
A very common, easily-fixed performance issue, srcset/sizes (or a Next.js <Image> component, which automates this) meaningfully reduces mobile data usage and improves load time.
Best Practices
- Always include the viewport meta tag, the literal first line of any responsive strategy.
- Author mobile-first, using
min-widthmedia queries that progressively enhance. - Let content dictate breakpoints, not device names or a framework's defaults, resize slowly and find where things actually break.
- Use
remfor typography (and generally most sizing), respecting user font-size preferences; useclamp()for fluid scaling without breakpoint jumps. - Use
srcset/sizesor a framework's automatic image optimization rather than a single fixed-resolution image for all viewports. - Test by dragging the viewport across the full range, not just jumping between device presets in DevTools.
- Prefer intrinsic/fluid layout techniques (Flexbox
wrap, Gridauto-fit/minmax, Container Queries) over an ever-growing pile of breakpoints where possible, fewer explicit breakpoints to maintain, more genuinely adaptive to content.
Further Resources
- MDN, Responsive design
- MDN, Using media queries
- web.dev, Responsive Web Design Basics
- CSS-Tricks, A Complete Guide to CSS Media Queries
- MDN, Responsive images
- Utopia, Fluid Responsive Design, tooling for fluid typography/spacing with
clamp().
