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-widthqueries read backwards.
/* 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); }
}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.
.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.
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.
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).
/* 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.
(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 + minmaxor Flexbox'sflex-wrap + basis. The layout adapts without breakpoints. - Fluid type with
clamp(). One line replaces a stack of media queries. - Strategic breakpoints with
min-widthmedia queries for things that simply can't flow (like a sidebar that has to collapse). - Input-aware styles using
hoverandpointermedia features. - Container queriesfor components that don't care about the viewport, only their own width. That's next lesson.
Quiz
Why prefer mobile-first (min-width) over desktop-first (max-width) queries?
Recap
- Mobile-first means base CSS for phones, enhancements via
min-widthqueries. - Use
dvhfor mobile heros that should track the URL bar. Layervhbeforedvhas a fallback. clamp(min, ideal, max)replaces stacks of typography media queries. Userem + vwfor 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.