Last 30 Days
No notifications
Responsive design ensures your site looks and works great on any device — from phones to ultra-wide monitors. The core tools are media queries, fluid units, and modern CSS features like container queries.
Write base styles for small screens first, then add complexity with min-width media queries:
/* Base = mobile */
.grid { display: flex; flex-direction: column; }/* Tablet and up */
@media (min-width: 768px) {
.grid { flex-direction: row; }
}
| Breakpoint | Target | Media Query |
| 480px | Small phones | @media (min-width: 480px) |
| 768px | Tablets | @media (min-width: 768px) |
| 1024px | Laptops | @media (min-width: 1024px) |
| 1280px | Desktops | @media (min-width: 1280px) |
Use clamp() to scale font sizes smoothly between a minimum and maximum:
h1 { font-size: clamp(1.5rem, 4vw, 3rem); }Container queries style children based on their parent's size (not the viewport):
.card-wrapper { container-type: inline-size; }@container (min-width: 400px) {
.card { flex-direction: row; }
}
<img
srcset="photo-400.jpg 400w, photo-800.jpg 800w"
sizes="(max-width: 600px) 100vw, 50vw"
src="photo-800.jpg"
alt="Responsive photo"
/>The browser picks the best source for the device's screen size and resolution.
In 2007 every website assumed a 1024-pixel desktop monitor. Then phones happened. Today over half of all web traffic is mobile, screens range from a 320 px watch face to an 8K TV, and you don't know which one your user is on. Responsive design means writing one layout that adapts to all of them — instead of building a separate "mobile site".
If you forget this single line, mobile browsers will pretend they're a 980 px desktop and shrink everything. Add it to every page's :
<meta name="viewport" content="width=device-width, initial-scale=1.0" />That's it. Now your CSS actually sees the real device width.
The trick is to design for the smallest screen first, then progressively add layout for bigger ones. Why? Because a small phone forces you to keep things simple, and "adding columns when there's space" is much easier than "removing columns when there's not".
/* Base styles — phone */
.card { padding: 12px; font-size: 1rem; }/* When the screen gets bigger, enhance */
@media (min-width: 768px) {
.card { padding: 24px; font-size: 1.125rem; }
}
Notice we use min-width, not max-width. That's the mobile-first signal: "starting from this width, do extra stuff."
A media query says "apply these styles only when the condition is true."
@media (min-width: 768px) { /* ≥ tablet */ }
@media (min-width: 1024px) { /* ≥ small laptop */ }
@media (min-width: 1280px) { /* ≥ desktop */ }
@media (prefers-color-scheme: dark) { /* user wants dark mode */ }
@media (prefers-reduced-motion: reduce) { /* user is sensitive to motion */ }There's no "official" set, but a sensible default mirrors Tailwind:
| Name | Min width | Roughly |
| sm | 640 px | Large phone / small tablet |
| md | 768 px | Tablet |
| lg | 1024 px | Small laptop |
| xl | 1280 px | Desktop |
| 2xl | 1536 px | Big monitor |
Don't invent breakpoints based on devices ("iPhone 14 is 390 px so I'll target 390 px"). Pick breakpoints based on where *your design* breaks.
Before reaching for a media query, ask if a fluid technique can do the job for free.
max-width for Containers.container { width: 100%; max-width: 1200px; margin-inline: auto; }The container is fluid up to 1200 px, then stops growing. One rule, zero media queries.
img, video { max-width: 100%; height: auto; display: block; }Images shrink to fit their parent and never overflow. Set this once, forget about it.
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 1rem;
}Cards reflow from 1 column on a phone to 5 columns on a desktop without a single @media.
| Unit | Description |
% | Percent of parent — flexible widths |
rem | Relative to root font-size — typography & spacing |
vw / vh | 1% of viewport — hero sections |
min(), max(), clamp() | Mix fixed + fluid in one value |
clamp() Function — Fluid Without Breakpointsclamp(min, preferred, max) reads as "be the *preferred* value, but never below *min* and never above *max*."
/* Font scales smoothly from 1rem on a phone to 2rem on a big screen */
h1 { font-size: clamp(1.5rem, 4vw, 3rem); }/* Container width that's fluid but bounded */
.wrapper { width: clamp(320px, 90vw, 1200px); }
This is the modern shortcut for "fluid typography" and replaces piles of media queries.
1. Hard-coding pixel widths on cards or sidebars. Use max-width instead.
2. Forgetting the viewport meta tag — site looks fine on Chrome DevTools, broken on a real phone.
3. Pixel media queries based on phones instead of where the design breaks.
4. Tiny tap targets. Buttons should be at least 44 × 44 px so fingers can hit them.
5. Hover-only interactions. Touch devices don't have hover; provide a tap or visible button.
Media queries respond to the *viewport*. Container queries respond to the *parent's size*. Why does this matter? Because the same card component might sit in a 300 px sidebar on one page and a 900 px main column on another — and you want it to look right in both, automatically.
.card { container-type: inline-size; }@container (min-width: 400px) {
.card { display: grid; grid-template-columns: 100px 1fr; }
}
The card decides its own layout based on the room *it* has, not the page.
srcsetDon't ship a 4 MB hero image to a phone:
<img
src="hero-800.jpg"
srcset="hero-400.jpg 400w, hero-800.jpg 800w, hero-1600.jpg 1600w"
sizes="(max-width: 600px) 100vw, 800px"
alt="…"
/>The browser picks the smallest file that still looks crisp. Free performance win.
For art-direction (different *crops* for phone vs desktop), use :
<picture>
<source media="(min-width: 800px)" srcset="hero-wide.jpg" />
<img src="hero-square.jpg" alt="…" />
</picture>Modern OSes expose user settings; CSS can read them.
@media (prefers-color-scheme: dark) {
:root { background: #111; color: #eee; }
}@media (prefers-reduced-motion: reduce) {
* { animation: none !important; transition: none !important; }
}
Your site instantly feels considerate without users changing a thing.
vh on mobile has a long-running gotcha — it includes the area behind the address bar, so 100vh overflows. CSS now has:
| Unit | Meaning |
svh | *Small* viewport — when address bar is showing |
lvh | *Large* viewport — when address bar is hidden |
dvh | *Dynamic* viewport — adjusts as the bar appears/disappears |
.hero { min-height: 100dvh; } /* always exactly the visible area */If your site might support Arabic, Hebrew, or vertical Japanese, use logical properties so layout flips automatically:
.card { padding-inline: 1rem; margin-block: 1rem; }inline = direction of text (left/right in English, right/left in Arabic). block = perpendicular (top/bottom).
@supports for Progressive EnhancementUse a fancy property only if the browser supports it; otherwise fall back gracefully:
.card { display: block; }@supports (display: grid) {
.card { display: grid; }
}
rem.1. Take a desktop-only landing page and convert it to mobile-first using only min-width media queries.
2. Replace every fixed font-size: 32px with a clamp() expression and watch text scale smoothly.
3. Build a card component that switches from stacked (icon above text) to inline (icon beside text) using a container query, not a media query.
4. Add prefers-color-scheme: dark and prefers-reduced-motion support to one of your existing projects.