Responsive Design & Media Queries
Responsive design means building websites that adapt to different screens, input methods, user preferences, and content sizes.
A responsive page should work on:
- small phones,
- large phones,
- tablets,
- laptops,
- desktop monitors,
- zoomed browsers,
- landscape and portrait orientations,
- and split-screen browser windows.
The goal is not to make one perfect layout for every device. The goal is to create layouts that remain readable, usable, and stable as available space changes.
1. Responsive Design Is Not Just Screen Size
Responsive design includes:
- layout changes,
- readable typography,
- flexible images,
- comfortable touch targets,
- accessible zoom behavior,
- support for different input devices,
- and respecting user settings such as reduced motion or dark mode.
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 1rem;
}
This grid responds without a media query because the columns are fluid.
2. The Viewport Meta Tag
Responsive design starts in the document <head>.
<meta name="viewport" content="width=device-width, initial-scale=1.0">
Without this tag, many mobile browsers pretend the page is around 980px wide and zoom the site out. Text becomes tiny and users must pinch to read.
Parts:
width=device-width: use the real device viewport width.initial-scale=1.0: start at normal zoom.
Do not disable zoom:
<!-- Bad: prevents users from zooming -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
Users need zoom for accessibility. Let them zoom.
3. Mobile-First CSS
Mobile-first means writing the smallest layout first, then adding larger layouts with min-width.
.layout {
display: grid;
gap: 1rem;
}
@media (min-width: 768px) {
.layout {
grid-template-columns: 260px 1fr;
}
}
Why mobile-first is recommended:
- The base CSS is simpler.
- Small screens get fewer overrides.
- Larger screens progressively add complexity.
- It matches how content should work: readable first, enhanced later.
Desktop-first uses max-width:
.layout {
display: grid;
grid-template-columns: 260px 1fr;
}
@media (max-width: 767px) {
.layout {
grid-template-columns: 1fr;
}
}
Desktop-first can work, but it often requires undoing complex desktop styles for mobile.
4. Media Query Syntax
Media queries apply CSS only when conditions match.
@media (min-width: 768px) {
.cards {
grid-template-columns: repeat(2, 1fr);
}
}
The general structure:
@media media-type and (condition) {
selector {
property: value;
}
}
Most modern CSS omits screen because screen is the common default target.
@media (min-width: 1024px) {
.page {
max-width: 1200px;
}
}
Range syntax is also available in modern CSS:
@media (600px <= width <= 900px) {
.preview {
background: #dbeafe;
}
}
The classic equivalent:
@media (min-width: 600px) and (max-width: 900px) {
.preview {
background: #dbeafe;
}
}
5. Common Media Features
Useful media features:
| Feature | Example | Use |
|---|---|---|
min-width | @media (min-width: 768px) | Mobile-first breakpoints |
max-width | @media (max-width: 767px) | Desktop-first overrides |
orientation | @media (orientation: landscape) | Landscape or portrait changes |
hover | @media (hover: hover) | Hover-capable devices |
pointer | @media (pointer: coarse) | Touch target adjustments |
prefers-reduced-motion | @media (prefers-reduced-motion: reduce) | Reduce animation |
prefers-color-scheme | @media (prefers-color-scheme: dark) | Theme preference |
prefers-contrast | @media (prefers-contrast: more) | Higher contrast styles |
Example for touch devices:
@media (pointer: coarse) {
.button {
min-height: 44px;
}
}
Example for hover:
@media (hover: hover) {
.card:hover {
transform: translateY(-3px);
}
}
This avoids hover-only interactions on touch devices.
6. Breakpoints
Breakpoints are widths where the layout needs to change.
Do not choose breakpoints only because a device exists. Choose them when your content starts to break.
Typical starting points:
| Label | Width | Common use |
|---|---|---|
| Small | 480px | Larger phones |
| Medium | 768px | Tablets |
| Large | 1024px | Laptops |
| Extra large | 1280px | Desktops |
| Wide | 1536px | Large monitors |
Example:
.cards {
display: grid;
gap: 1rem;
}
@media (min-width: 640px) {
.cards {
grid-template-columns: repeat(2, 1fr);
}
}
@media (min-width: 1024px) {
.cards {
grid-template-columns: repeat(3, 1fr);
}
}
The right question is: "At what width does this layout become awkward?"
7. Fluid Layouts Before Breakpoints
Responsive design works best when the layout is fluid before breakpoints are added.
.container {
width: min(100% - 2rem, 1200px);
margin-inline: auto;
}
This means:
- Use full available width minus
2rem. - Never exceed
1200px. - Center the container.
Fluid grid:
.course-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 1rem;
}
This often removes the need for several breakpoints.
8. Viewport Units
Viewport units are based on the browser viewport.
| Unit | Meaning |
|---|---|
vw | 1% of viewport width |
vh | 1% of viewport height |
vmin | 1% of the smaller viewport dimension |
vmax | 1% of the larger viewport dimension |
svh | small viewport height |
lvh | large viewport height |
dvh | dynamic viewport height |
Use modern viewport height units for mobile layouts:
.hero {
min-height: 100svh;
}
100vh can be awkward on mobile because browser address bars appear and disappear. svh, lvh, and dvh give more control.
9. Fluid Typography with clamp()
clamp() gives a value a minimum, preferred value, and maximum.
h1 {
font-size: clamp(2rem, 6vw, 5rem);
}
Meaning:
minimum: 2rem
preferred: 6vw
maximum: 5rem
Use it for headings, spacing, and layout sizes.
.section {
padding-block: clamp(3rem, 8vw, 7rem);
}
clamp() is often better than many typography breakpoints.
10. Responsive Images
Images should not overflow their container.
img {
max-width: 100%;
height: auto;
}
For fixed-shape image cards:
.thumbnail {
aspect-ratio: 16 / 9;
object-fit: cover;
width: 100%;
}
HTML can also provide multiple image sources:
<picture>
<source srcset="hero-wide.jpg" media="(min-width: 900px)">
<img src="hero-small.jpg" alt="Students learning CSS">
</picture>
Use srcset and sizes when you want the browser to choose the best image size:
<img
src="course-800.jpg"
srcset="course-400.jpg 400w, course-800.jpg 800w, course-1200.jpg 1200w"
sizes="(min-width: 1024px) 33vw, (min-width: 640px) 50vw, 100vw"
alt="Course preview">
11. Responsive Navigation
Navigation often changes shape across screen sizes.
.nav {
display: flex;
flex-direction: column;
gap: 1rem;
}
@media (min-width: 768px) {
.nav {
flex-direction: row;
align-items: center;
justify-content: space-between;
}
}
Mobile navigation should be:
- easy to tap,
- keyboard accessible,
- readable at zoom,
- and not dependent on hover.
Touch targets should usually be at least around 44px high.
.nav-link {
min-height: 44px;
display: inline-flex;
align-items: center;
}
12. Responsive Tables
Wide tables are hard on small screens. A simple solution is horizontal scrolling.
<div class="table-scroll">
<table>
...
</table>
</div>
.table-scroll {
overflow-x: auto;
}
.table-scroll table {
min-width: 640px;
}
Do not shrink table text until it becomes unreadable. Scrolling is often better than destroying the table structure.
13. Container Queries
Media queries respond to the viewport. Container queries respond to a component's parent container.
.course-card-wrapper {
container-type: inline-size;
}
.course-card {
display: grid;
gap: 1rem;
}
@container (min-width: 460px) {
.course-card {
grid-template-columns: 160px 1fr;
}
}
This lets the same component adapt whether it appears in a narrow sidebar or a wide main content area.
Container queries are covered again in the modern CSS chapter, but they are important for responsive design too.
14. Responsive Accessibility
A responsive site must remain accessible.
Check:
- Text remains readable at
200%zoom. - Layout does not require horizontal scrolling for normal content.
- Buttons and links are easy to tap.
- Focus states remain visible.
- Content order still makes sense.
- Hidden menus are keyboard accessible.
- Hover-only behavior has a touch and keyboard alternative.
Bad:
.card {
height: 180px;
overflow: hidden;
}
This may clip text when users increase font size.
Better:
.card {
min-height: 180px;
}
Let content grow when needed.
15. Testing Responsive Design
Use multiple testing methods:
- Resize the browser manually.
- Use DevTools responsive mode.
- Test real mobile devices when possible.
- Zoom the page to
200%. - Test keyboard navigation.
- Test landscape orientation.
- Test with slow network conditions.
- Check long words, long names, and real content.
Responsive bugs often appear between common device sizes, not exactly at common device sizes. Drag the viewport slowly and watch where the layout breaks.
16. Debugging Checklist
When a layout breaks on smaller screens, ask:
- Is the viewport meta tag present?
- Is any element wider than the viewport?
- Is a fixed width causing overflow?
- Would
max-width: 100%fix images or media? - Would
minmax(0, 1fr)fix grid overflow? - Would
min-width: 0fix flex overflow? - Is text clipped by fixed height?
- Are breakpoints based on content, not device names?
- Can
clamp()reduce breakpoint complexity? - Would container queries make the component more reusable?
17. Complete Example: Responsive Course Layout
.course-page {
width: min(100% - 2rem, 1180px);
margin-inline: auto;
padding-block: clamp(2rem, 6vw, 5rem);
}
.course-hero {
display: grid;
gap: 1.5rem;
}
.course-title {
font-size: clamp(2.25rem, 7vw, 5rem);
line-height: 1;
}
.course-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 1rem;
}
.course-image {
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover;
border-radius: 1rem;
}
@media (min-width: 900px) {
.course-hero {
grid-template-columns: minmax(0, 1fr) 360px;
align-items: center;
}
}
This layout uses:
- fluid container width,
- fluid section spacing,
- fluid heading size,
- responsive card grid,
- responsive media,
- and one breakpoint for the hero layout.
18. Mini Practice
Build a responsive card grid:
- One column by default.
- Two columns at
640px. - Three columns at
1024px. - Gap of
1rem.
Possible answer:
.cards {
display: grid;
gap: 1rem;
}
@media (min-width: 640px) {
.cards {
grid-template-columns: repeat(2, 1fr);
}
}
@media (min-width: 1024px) {
.cards {
grid-template-columns: repeat(3, 1fr);
}
}
Now try the fluid version without those breakpoints:
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 1rem;
}
Both approaches are valid. The second is often simpler when exact column counts are not required.