Responsive CSS That Works Everywhere (Not Pixel-Perfect Everywhere)
Mobile-first media queries, fluid typography with clamp(), container queries, responsive images, and viewport units. Practical responsive design for real projects.
Here's the mindset shift that makes responsive design click: you're not building separate layouts for phone, tablet, and desktop. You're building one flexible layout that doesn't break at any width. Breakpoints are just nudges to fix things when the default flex starts looking awkward.
Mobile-First: Start Small, Add Complexity
Write your base CSS for the smallest screen. Then use min-width media queries to add layout changes as the viewport grows.
/ Base: single column, works on any phone /
.grid {
display: grid;
gap: 16px;
grid-template-columns: 1fr;
}
/ Tablet-ish: two columns /
@media (min-width: 640px) {
.grid {
grid-template-columns: repeat(2, 1fr);
}
}
/ Desktop-ish: three columns /
@media (min-width: 1024px) {
.grid {
grid-template-columns: repeat(3, 1fr);
}
}
Why mobile-first instead of desktop-first (max-width)? Because mobile is the constrained case. Start with what works in 320px of space, then add layout features when you have room. Going the other way means constantly undoing desktop styles for mobile, which gets messy fast.
Common Breakpoints (And Why They're Guidelines)
/ Small phones: 0 - 639px (your base styles) /
/ Tablets: 640px+ /
/ Small laptops: 1024px+ /
/ Desktops: 1280px+ /
/ Wide screens: 1536px+ /
These aren't sacred numbers. They come from Tailwind's defaults, which are roughly based on common device widths. But devices are a spectrum now -- there's no clear line between "phone" and "tablet." Add a breakpoint when your layout looks broken, not because a chart told you to.
Fluid Typography with clamp()
Hardcoded font sizes at each breakpoint create jarring jumps. clamp() gives you smooth scaling:
h1 {
font-size: clamp(1.75rem, 4vw + 0.5rem, 3.5rem);
}
p {
font-size: clamp(1rem, 0.5vw + 0.875rem, 1.125rem);
}
clamp(minimum, preferred, maximum) -- the browser uses the preferred value (which scales with viewport width) but never goes below the minimum or above the maximum.
The 4vw + 0.5rem part is the scaling formula. The vw component makes it grow with the viewport, the rem component keeps it from being absurdly tiny on small screens. There are calculators online that help you generate these values, but honestly, eyeball it and adjust until it looks right at 320px and 1440px.
This works for spacing too:
.section {
padding: clamp(24px, 5vw, 80px);
}
Container Queries: The New Hotness
Media queries respond to the viewport width. Container queries respond to the parent element's width. This is a game-changer for component-based design.
.card-container {
container-type: inline-size;
}
.card {
display: grid;
grid-template-columns: 1fr;
}
@container (min-width: 400px) {
.card {
grid-template-columns: 150px 1fr;
}
}
Now the card switches to a horizontal layout when its container is 400px wide -- regardless of the viewport. Put that card in a sidebar and it stays vertical. Put it in a wide main column and it goes horizontal. The card doesn't need to know where it lives.
Container queries landed in all major browsers in 2023 and they're stable now. If you're building a component library, they're the right tool. For page-level layout, media queries are still fine.
Responsive Images
Images are often the biggest performance bottleneck on mobile. Three tools to know:
srcset for resolution switching
<img
src="photo-800.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1200.jpg 1200w"
sizes="(min-width: 1024px) 33vw, (min-width: 640px) 50vw, 100vw"
alt="A photo"
/>
The browser picks the best image based on viewport width and screen density. A phone on 3G gets the 400px version. A retina desktop gets the 1200px version. You provide the options; the browser decides.
picture element for art direction
<picture>
<source media="(min-width: 1024px)" srcset="hero-wide.jpg" />
<source media="(min-width: 640px)" srcset="hero-medium.jpg" />
<img src="hero-mobile.jpg" alt="Hero image" />
</picture>
Use when you want completely different images at different sizes -- a cropped version for mobile, a panoramic version for desktop. srcset is for the same image at different resolutions.
CSS object-fit
.cover-image {
width: 100%;
height: 300px;
object-fit: cover;
}
object-fit: cover is the CSS equivalent of background-size: cover. The image fills its container without distortion, cropping the excess. Essential for card thumbnails and hero sections.
Viewport Units
vw/vh-- 1% of viewport width / heightdvh-- dynamic viewport height. On mobile browsers, the address bar appears and disappears as you scroll.100vhis the largest viewport height (with bar hidden), which means content overflows when the bar is visible.100dvhadjusts to the current viewport height.
.hero {
min-height: 100dvh; / actually fills the visible screen on mobile /
}
Use dvh for full-screen sections on mobile. Use vh for desktop-only layouts or when the dynamic resizing would cause jank.
There's also svh (smallest viewport height -- with bar visible) and lvh (largest -- with bar hidden), if you need to lock to one or the other.
Practical Pattern: Max-Width Wrapper
Almost every site needs a centered container that maxes out at some width:
.wrapper {
width: min(90%, 1200px);
margin-inline: auto;
}
min() picks the smaller value. On mobile, 90% is smaller, so you get padding on the sides. On desktop, 1200px is smaller, so content doesn't stretch to the edge of a ultrawide monitor. No media query needed.
The Goal
Responsive design isn't about making your site look identical on every device. That's not possible and not worth pursuing. The goal is: content is readable, nothing overflows, nothing is unreachably small, and the layout makes sense at every width. Test by slowly dragging your browser window from 320px to 1600px. If nothing breaks or looks absurd at any point, you're done.
The best way to internalize all of this is to build responsive layouts from scratch, not by tweaking a template. CodeUp has CSS layout challenges that you can work through in the browser -- start with a mobile-first card grid and work up to a full responsive page.