webdev.complete
📱 Responsive & Modern CSS
🎨CSS Power
Lesson 17 of 117
25 min

Mobile-First Responsive

Media queries, viewport units, fluid type with clamp().

Once upon a time, web designers had to pick "the right screen width" to design for. 800 by 600. Then 1024 by 768. Then, well, everyone gave up because phones happened, tablets happened, foldables happened, and now you have a fridge that runs Chrome. Responsive design is the philosophy that says: stop guessing the screen, let the screen guess you.

Mobile-first: the default that pays for itself

Mobile-first means you write your base CSS as if every visitor were on a 360px phone. Then you progressively enhance for larger screens with min-width media queries. This pays off in three ways:

  • Phones get the simplest CSS, which is the smallest payload. That's the right tradeoff for the slowest connections.
  • You start with the constrained design and add things, which forces you to keep the mobile experience first-class.
  • min-widthqueries read as a story: "at this breakpoint and up, do this." max-width queries read backwards.
css
/* Base styles: mobile first. */
.card { padding: 16px; }
.grid { display: block; }

/* At 640px and up: tablet enhancements */
@media (min-width: 640px) {
  .card { padding: 24px; }
  .grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 16px;
  }
}

/* At 1024px and up: desktop */
@media (min-width: 1024px) {
  .grid { grid-template-columns: repeat(3, 1fr); }
}
The breakpoints that actually matter
Stop chasing exact device widths. Pick breakpoints where your design breaks. Common starting set: 640px (small tablet), 768px (tablet), 1024px (small laptop), 1280px (desktop). Adjust as your layout demands, not based on whatever phone you happen to use.

Viewport units: vw, vh, dvh, svh

Viewport units measure relative to the browser viewport. 1vw is 1% of the viewport width, 1vh is 1% of the viewport height. Useful, but the original vhhas a famous problem on mobile: the browser's URL bar slides in and out, and 100vhdoesn't track with it. Your full-screen hero ends up scrolled or cut off.

The fix shipped in 2022 and is now stable. Three new viewport units let you opt into different behaviors:

  • svh = small viewport height. Assumes the URL bar is visible. Smallest.
  • lvh = large viewport height. Assumes the URL bar is hidden. Largest.
  • dvh= dynamic viewport height. Updates as the URL bar slides. The one you usually want for "full-screen heroes" on mobile.
css
.hero {
  min-height: 100dvh;   /* dynamic: tracks the URL bar */
}

/* For older browsers, layered fallback */
.hero {
  min-height: 100vh;     /* old, may overflow */
  min-height: 100dvh;    /* modern, wins where supported */
}

Fluid typography with clamp()

Before clamp(), you had three or four media queries to scale a heading across breakpoints. clamp(min, ideal, max) gives you all of that in one line. The browser picks ideal unless it's below min or above max, in which case it's clamped.

css
h1 {
  /* Never smaller than 24px. Never larger than 56px.
     Otherwise it scales with viewport. */
  font-size: clamp(24px, 4vw + 1rem, 56px);
}

Notice the middle value uses vw + rem. That makes the text scale with the viewport but also responds to the user's browser font-size setting (a key accessibility feature). Pure vw alone ignores user preferences.

clamp() typography
Hello, fluid type!
rendered at 30.0px (ideal would be 30.0px)
h1 { font-size: clamp(18px, 5vw, 48px); }

Beyond width: hover and pointer media queries

Width is the obvious thing to query, but it lies. A 13-inch tablet with a stylus has the same width as a laptop with a mouse. You need more information. @media (hover) and @media (pointer) ask the right questions:

  • (hover: hover) matches devices that can hover. Mice, trackpads, styluses with a hover state.
  • (hover: none) matches devices that cannot hover. Touchscreens, mostly.
  • (pointer: fine) matches precise pointing (mouse, stylus).
  • (pointer: coarse) matches imprecise pointing (a finger).
css
/* Only show the hover effect on devices that can hover */
@media (hover: hover) {
  .button:hover { background: #4f46e5; }
}

/* Make tap targets larger on touch devices */
@media (pointer: coarse) {
  .button { min-height: 44px; padding: 12px 20px; }
}

On a touchscreen, :hover"sticks" until the user taps somewhere else, which is bad UX. Gating hover styles by (hover: hover) fixes this for real.

Don't rely on width to detect touch
A Surface Pro with touch and a Bluetooth mouse has both fine and coarse pointers. Use (any-pointer: coarse)if you want "has any touch input," and (pointer: coarse) for "primarily a touch device."

The full responsive toolkit

Modern responsive design isn't one technique, it's several layers used together:

  • Fluid layouts with Grid's auto-fit + minmax or Flexbox's flex-wrap + basis. The layout adapts without breakpoints.
  • Fluid type with clamp(). One line replaces a stack of media queries.
  • Strategic breakpoints with min-width media queries for things that simply can't flow (like a sidebar that has to collapse).
  • Input-aware styles using hover and pointer media features.
  • Container queriesfor components that don't care about the viewport, only their own width. That's next lesson.

Quiz

Quiz1 / 4

Why prefer mobile-first (min-width) over desktop-first (max-width) queries?

Recap

  • Mobile-first means base CSS for phones, enhancements via min-width queries.
  • Use dvh for mobile heros that should track the URL bar. Layer vh before dvh as a fallback.
  • clamp(min, ideal, max) replaces stacks of typography media queries. Use rem + vw for the ideal value to respect user font sizes.
  • @media (hover) and @media (pointer) tell you about input, not just screen size.
  • Modern responsive = fluid layouts + fluid type + a few strategic breakpoints + input-aware styles.
Built with Next.js, Tailwind & Sandpack.
Learn. Build. Ship.