Module 6: Responsive Design & Media Queries
Fluid Layouts, Responsive Units and Responsive Images
How to build layouts that stretch instead of snap, scale type with clamp(), and ship images that fit the screen and do not shift the page while loading.
Media queries make a layout jump at certain widths. Between those widths, a good layout should flex smoothly. This lesson covers the tools that make that happen: fluid sizing, the min(), max() and clamp() functions, and the handling of images, which are the heaviest and most layout-breaking thing on most pages.
Fluid containers#
The basic building block is a container that grows with the screen but stops at a sensible limit.
.container {
width: min(100% - 2rem, 70rem); /* full width minus gutters, capped at 70rem */
margin-inline: auto;
}
min() takes the smallest of its arguments. On a narrow screen, 100% - 2rem is smaller, so the container shrinks with the viewport and leaves a 1rem gutter on each side. On a wide screen, 70rem is smaller, so it stops growing and centers.
The same job with the older properties looks like this:
.container {
width: 100%;
max-width: 70rem;
padding-inline: 1rem;
margin-inline: auto;
}
Either is fine. The key is to use max-width or min() for the limit, never a fixed width that forces overflow.
min(), max() and clamp()#
width: min(90%, 40rem); /* the smaller of the two */
font-size: max(1rem, 2vw); /* the larger: never below 1rem */
padding: clamp(1rem, 4vw, 3rem);
clamp(minimum, preferred, maximum) gives you a value that follows the middle argument but stays inside the bounds. It is a media query in one line.
clamp(1rem, 4vw, 3rem)
size
3rem │ ┌──────────────
│ ╱
│ ╱ ← grows with 4vw
1rem │───────────────╱
└──────────────────────────────► viewport width
← min → ← fluid → ← max →
Fluid typography#
h1 { font-size: clamp(2rem, 1.2rem + 3vw, 4rem); }
p { font-size: clamp(1rem, 0.95rem + 0.25vw, 1.125rem); }
Mix a rem part with a vw part in the preferred value. A pure vw size ignores the user's font size setting and cannot be zoomed, which is an accessibility failure. Adding a rem component keeps text zoomable. Test it by zooming the page to 200%.
Images that adapt#
The base rule#
img, video, svg {
max-width: 100%;
height: auto;
display: block;
}
An image has a natural size. Without max-width: 100%, a 1600px photo overflows a 400px container. With it, the image shrinks to fit but never stretches beyond its natural size. height: auto keeps the proportions.
display: block removes the small gap below inline images, which exists because an inline image sits on the text baseline and leaves room for descenders.
Prevent layout shift#
Put width and height attributes on every image, matching its natural pixel dimensions:
<img src="team.jpg" alt="The team at the 2025 offsite" width="1200" height="800">
With the CSS above, they do not set the displayed size. Browsers use them to compute an aspect ratio before the image loads, and reserve the right amount of space. Without them, text jumps down when the image arrives, which is the layout shift users complain about.
aspect-ratio and object-fit#
.thumb {
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover; /* fill the box, crop the excess */
object-position: center top;
}
aspect-ratiokeeps a box in proportion without the old padding-percentage hack.object-fit: coverworks for images likebackground-size: coverworks for backgrounds: the image fills the box and is cropped.containfits it entirely and can leave gaps.object-positionchooses which part stays visible when cropping, which is useful for portraits where the face should not be cut off.
Serving the right file: srcset and sizes#
A phone does not need a 2400px image. srcset offers the browser several files, and it picks one:
<img
src="photo-800.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1600.jpg 1600w"
sizes="(min-width: 1024px) 33vw, (min-width: 640px) 50vw, 100vw"
alt="Mountain trail at sunrise"
width="1600" height="1067"
loading="lazy">
srcsetlists the candidates and their real widths (w).sizestells the browser how wide the image will be displayed at different viewport widths. The browser reads this before CSS has loaded, which is why you tell it separately.- The browser combines
sizes, the screen's pixel density and its own heuristics to choose a file. You do not control exactly which one it picks. loading="lazy"delays offscreen images. Do not use it on the first image above the fold, which should load as soon as possible.
Art direction with picture#
When you want a different crop at different sizes, not just a different resolution, use picture:
<picture>
<source media="(min-width: 800px)" srcset="hero-wide.avif" type="image/avif">
<source media="(min-width: 800px)" srcset="hero-wide.jpg">
<img src="hero-square.jpg" alt="Our workshop" width="800" height="800">
</picture>
The browser picks the first source that matches. The img is the fallback and the element that actually renders, so it needs the alt. The type attribute lets you offer modern formats (AVIF, WebP) with a JPEG fallback.
CSS background images#
Backgrounds cannot use srcset, but you can swap them in media queries:
.hero { background-image: url("hero-small.jpg"); }
@media (min-width: 800px) { .hero { background-image: url("hero-large.jpg"); } }
Other responsive details#
.video-wrap { aspect-ratio: 16 / 9; }
.video-wrap iframe { width: 100%; height: 100%; }
table { display: block; overflow-x: auto; } /* wide tables scroll, the page does not */
Common mistakes#
- Pure
vwfont sizes, which do not respond to user zoom. - Fixed pixel widths on containers and images.
- Missing
widthandheighton images, causing layout shift. width: 100vwcreating horizontal scroll.- Lazy-loading the hero image, which slows the most important paint.
- Serving one huge image to every device.
- Setting
heighton images explicitly withwidth: 100%, which stretches and distorts them. Useheight: autooraspect-ratiowithobject-fit.
Practice#
- Build a fluid container with
min(). Resize the window and confirm the gutters and the cap. - Create a heading and body text with
clamp(). Test at 320px, 1200px and with browser zoom at 200%. - Build a card grid where every thumbnail has
aspect-ratio: 4 / 3andobject-fit: cover, using photos of mixed proportions. - Add
srcsetandsizesto an image with three file sizes. In the Network tab, check which file loads at different window widths and device pixel ratios.