Responsive design means building layouts that adapt to any screen size — from a phone to a widescreen monitor. The goal is one codebase that works everywhere.
The Viewport Meta Tag
This tag is required for responsive design to work on mobile devices:
<meta name="viewport" content="width=device-width, initial-scal=1.0">
Without it, mobile browsers render the page at a desktop width (typically 980px) and then zoom out, making everything tiny.
Mobile-First Approach
Write your base styles for small screens, then add complexity for larger ones. This keeps mobile styles simple and avoids overriding desktop styles on smaller devices:
/* Base: single column for mobile */
.grid {
display: grid;
grid-template-columns: 1fr;
gap: 1rem;
}
/* Tablet: 2 columns */
@media (min-width: 768px) {
.grid {
grid-template-columns: repeat(2, 1fr);
}
}
/* Desktop: 3 columns */
@media (min-width: 1024px) {
.grid {
grid-template-columns: repeat(3, 1fr);
}
}Common breakpoints:
640px— small tablets, large phones in landscape768px— tablets1024px— small laptops1280px— desktops
These are guidelines, not rules. Set breakpoints where your design breaks, not at specific devices.
Media Queries
Media queries conditionally apply styles based on viewport characteristics:
/* Width-based (most common) */
@media (min-width: 768px) {
.sidebar { display: block; }
}
/* Range syntax (modern, more readable) */
@media (768px <= width < 1024px) {
.container { max-width: 720px; }
}
/* Orientation */
@media (orientation: landscape) {
.hero { min-height: 50dvh; }
}
/* Preference: reduced motion */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
/* Preference: dark mode */
@media (prefers-color-scheme: dark) {
:root {
--color-bg: #0a0a0a;
--color-text: #e5e5e5;
}
}
/* Hover capability — only apply hover effects on devices that support it */
@media (hover: hover) {
.card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
}Fluid Typography
Instead of setting fixed font sizes at breakpoints, use clamp() to create type that scales smoothly with the viewport:
h1 {
/* Minimum 2rem, scales with viewport, maximum 4rem */
font-size: clamp(2rem, 5vw + 1rem, 4rem);
}
p {
font-size: clamp(1rem, 0.5vw + 0.875rem, 1.25rem);
}The clamp(min, preferred, max) function picks the middle value, clamped between the minimum and maximum. This eliminates the need for font-size media queries in most cases.
Fluid spacing
The same technique works for spacing:
.section {
padding: clamp(2rem, 5vw, 6rem) clamp(1rem, 3vw, 4rem);
}
.grid {
gap: clamp(1rem, 2vw, 2rem);
}Responsive Images
Images should never overflow their container and should load at an appropriate size:
img {
max-width: 100%;
height: auto;
display: block;
}For art direction or performance, use srcset and sizes to serve different images at different viewport widths:
<img
src="photo-800.jpg"
srcset="
photo-400.jpg 400w,
photo-800.jpg 800w,
photo-1200.jpg 1200w
"
sizes="
(min-width: 1024px) 50vw,
100vw
"
alt="A landscape photo"
>
The browser picks the best image based on viewport width and device pixel ratio.
For different image crops at different sizes, use <picture>:
<picture>
<source media="(min-width: 768px)" srcset="hero-wide.jpg">
<source media="(min-width: 480px)" srcset="hero-medium.jpg">
<img src="hero-small.jpg" alt="Hero image">
</picture>Container Queries
Container queries let components respond to their parent container's size instead of the viewport. This makes components truly reusable — a card can behave differently in a sidebar versus a main content area:
/* Define a containment context */
.card-wrapper {
container-type: inline-size;
container-name: card;
}
/* Style the card based on its container's width */
@container card (min-width: 400px) {
.card {
display: flex;
flex-direction: row;
gap: 1rem;
}
.card-image {
width: 40%;
}
}
@container card (max-width: 399px) {
.card {
display: flex;
flex-direction: column;
}
.card-image {
width: 100%;
}
}<!-- In a narrow sidebar: card stacks vertically -->
<aside class="card-wrapper" style="width: 300px;">
<div class="card">
<img class="card-image" src="photo.jpg" alt="">
<div class="card-body">
<h3>Title</h3>
<p>Description</p>
</div>
</div>
</aside>
<!-- In main content: card lays out horizontally -->
<div class="card-wrapper" style="width: 600px;">
<div class="card">
<img class="card-image" src="photo.jpg" alt="">
<div class="card-body">
<h3>Title</h3>
<p>Description</p>
</div>
</div>
</div>Responsive Layout Patterns
Sidebar that collapses on mobile
.layout {
display: grid;
grid-template-columns: 1fr;
}
@media (min-width: 768px) {
.layout {
grid-template-columns: 250px 1fr;
}
}Navigation: horizontal on desktop, bottom bar on mobile
.nav {
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
justify-content: space-around;
padding: 0.5rem;
background: white;
border-top: 1px solid #ddd;
}
@media (min-width: 768px) {
.nav {
position: static;
border-top: none;
justify-content: flex-end;
gap: 2rem;
padding: 1rem 2rem;
}
}Responsive text truncation
.title {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* On larger screens, allow wrapping */
@media (min-width: 768px) {
.title {
white-space: normal;
overflow: visible;
}
}Testing Responsive Designs
- Browser DevTools — toggle the device toolbar to simulate different viewport sizes.
- Real devices — always test on an actual phone. Emulators miss touch targets, scrolling behavior, and browser chrome.
- Resize your browser window — drag the edge to see how layouts reflow. Look for awkward breaks.
- Check at every width — don't just test at breakpoints. Content between breakpoints matters too.