Responsive Images in CSS: srcset, sizes, and object-fit

- Responsive images, in one sentence
- Step one: make images stop overflowing
- srcset and sizes explained
- Art direction: the <picture> element
- object-fit: control how an image fills its box
- Reserve space with aspect-ratio and prevent layout shift
- Lazy loading and fetch priority
- A production-ready pattern
- Quick reference
Responsive images, in one sentence
A responsive image ships the right file to the right screen and then fits its box without distorting. In practice that means two separate jobs: the HTML srcset and sizes attributes let the browser download an appropriately sized file, and CSS properties like max-width, object-fit, and aspect-ratio control how that file displays once it arrives. You need both. srcset saves bandwidth; CSS controls layout. Get them working together and a single <img> looks crisp on a 320px phone and a 4K monitor while sending each device only the bytes it actually needs.
Here is the smallest responsive image that behaves well:
<img
src="photo-800.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1600.jpg 1600w"
sizes="(max-width: 700px) 100vw, 700px"
alt="A description of the photo"
width="1600"
height="900"
/>
img {
max-width: 100%;
height: auto;
}
Everything below explains why each piece is there and when to reach for a different tool.
Step one: make images stop overflowing
Before anything clever, images need to respect their container. A raw <img> renders at its intrinsic pixel width and will blow past a narrow layout. The fix is two lines that belong in nearly every stylesheet:
img {
max-width: 100%;
height: auto;
}
max-width: 100% stops the image from ever being wider than its parent. height: auto lets the height scale in proportion so the picture never squashes. Use max-width rather than width: 100% so small images aren't stretched larger than their real resolution, which would make them blurry.
That alone makes images fluid. But fluid is not the same as responsive: the browser is still downloading one big file and shrinking it in the viewport. A phone on a slow connection just paid for pixels it will never show. That is the problem srcset solves.
srcset and sizes explained
srcset gives the browser a menu of image files at different widths. sizes tells the browser how much layout space the image will occupy. The browser combines the two, factors in the device's pixel density, and picks the smallest file that still looks sharp — before it downloads anything.
The width descriptor (w) syntax
This is the common case: same image, multiple resolutions.
<img
src="hero-800.jpg"
srcset="
hero-400.jpg 400w,
hero-800.jpg 800w,
hero-1200.jpg 1200w,
hero-1600.jpg 1600w"
sizes="(max-width: 600px) 100vw,
(max-width: 1000px) 50vw,
800px"
alt="Mountain landscape at dawn"
width="1600"
height="900"
/>
Reading srcset: each entry is a filename plus a width descriptor — 800w means "this file is 800 pixels wide." You are describing the real pixel width of each file, not telling the browser which to use.
Reading sizes: it is a list of (media-condition) width pairs, evaluated left to right, with a final fallback width and no condition. This example says: below 600px the image fills the viewport (100vw); below 1000px it takes half (50vw); otherwise it is a fixed 800px wide. The browser takes the first matching rule.
The browser's math is roughly: slot width from sizes × device pixel ratio = the ideal image width, then it grabs the nearest srcset file at or above that number. On a 375px phone at 2× density needing a full-width image, that's about 750px, so it downloads hero-800.jpg — not the 1600px file.
src remains as a fallback for very old browsers that ignore srcset. width and height should match the aspect ratio of your files so the browser can reserve space and avoid layout shift.
The pixel-density descriptor (x) syntax
When an image is always displayed at a fixed CSS size — an avatar, an icon, a logo — you don't need sizes. Use x descriptors instead:
<img
src="avatar.jpg"
srcset="avatar.jpg 1x, [email protected] 2x, [email protected] 3x"
alt="User avatar"
width="64"
height="64"
/>
A standard display uses 1x; a Retina/high-DPI display grabs 2x. Simpler, but only correct when the rendered size never changes across breakpoints.
w vs x: which descriptor to use
| Situation | Descriptor | Need sizes? |
|---|---|---|
| Image width changes with the viewport (hero, article body, cards) | w |
Yes |
| Image is always the same CSS size (avatar, icon, logo) | x |
No |
| You only have one file | neither | No |
Do not mix w and x descriptors in the same srcset — the browser will ignore the malformed entries.
Common srcset mistakes
- Omitting
sizeswithwdescriptors. Without it the browser assumes100vwand often over-downloads. Always pairwwithsizes. - Lying in
sizes. If you claim400pxbut CSS renders the image at800px, high-DPI screens look soft. Keepsizesroughly honest to your real layout. - Too few steps. Three to five widths spanning your smallest and largest render sizes is plenty; dozens of files add build cost for little gain.
Art direction: the <picture> element
srcset swaps resolutions of the same image. When you need a genuinely different crop or image at different breakpoints — a wide landscape on desktop, a tight vertical crop on mobile — reach for <picture> with <source> and media:
<picture>
<source
media="(max-width: 600px)"
srcset="hero-portrait-600.jpg 600w, hero-portrait-1200.jpg 1200w"
/>
<source
media="(min-width: 601px)"
srcset="hero-wide-1000.jpg 1000w, hero-wide-2000.jpg 2000w"
/>
<img
src="hero-wide-1000.jpg"
alt="Team celebrating a launch"
width="2000"
height="1000"
/>
</picture>
The <img> inside is required — it's the fallback and the element that actually renders, carrying your alt text. <picture> is also the standard way to serve modern formats with a fallback:
<picture>
<source type="image/avif" srcset="photo.avif" />
<source type="image/webp" srcset="photo.webp" />
<img src="photo.jpg" alt="Description" width="1200" height="800" />
</picture>
The browser uses the first <source> whose type it supports, falling back to the JPEG. Use srcset for resolution switching and <picture> for art direction or format switching — that distinction keeps your markup honest.
object-fit: control how an image fills its box
Once an image has a fixed box — say a card thumbnail that must be 300×200 regardless of the photo's shape — plain <img> will distort it. object-fit tells the image how to fill that box without stretching. It behaves like background-size but for real <img> elements.
.card__image {
width: 100%;
height: 200px;
object-fit: cover;
}
object-fit values compared
| Value | What it does | Distorts? | Crops? |
|---|---|---|---|
fill (default) |
Stretches to fill the box exactly | Yes | No |
cover |
Scales to cover the box, cropping overflow | No | Yes |
contain |
Scales to fit entirely inside the box | No | No (may letterbox) |
none |
Keeps intrinsic size, ignores the box | No | Yes (if larger) |
scale-down |
Whichever of none/contain is smaller |
No | No |
object-fit: cover is the workhorse. It fills the box completely and crops whatever doesn't fit, so a portrait and a landscape photo both sit cleanly in the same fixed-size slot — exactly what you want for uniform card grids and avatars. contain is the choice when you must show the whole image (product shots, logos) and can accept empty space around it.
object-position: choose what gets cropped
With cover, the browser crops evenly from the center by default. object-position moves the visible region so a face or focal point survives the crop:
.card__image {
width: 100%;
height: 200px;
object-fit: cover;
object-position: top; /* keep the top; crop the bottom */
}
It takes the same values as background-position — keywords like top/center, or explicit values like object-position: 50% 20%.
object-fit is supported in every modern browser and has been safe to use unprefixed for years. Older Internet Explorer never supported it, but if you're not targeting IE, ship it freely.
Reserve space with aspect-ratio and prevent layout shift
Images that pop in after the text has rendered cause cumulative layout shift — content jumps as each picture loads. Two defenses:
Set width and height on the <img>. Modern browsers use them only to compute the aspect ratio and reserve space, then your CSS max-width: 100%; height: auto takes over for actual sizing. This costs nothing and is the single easiest layout-shift fix.
Or set aspect-ratio in CSS when the dimensions live in your stylesheet:
.card__image {
width: 100%;
aspect-ratio: 3 / 2;
object-fit: cover;
}
aspect-ratio is supported across all current browsers. Combined with object-fit: cover, it locks the box shape and crops the photo to match — no reserved-space guesswork.
Lazy loading and fetch priority
Two HTML attributes round out a performance-minded image. They live on the <img>, not in CSS, but they belong in any responsive-images checklist:
loading="lazy"defers off-screen images until the user scrolls near them. Perfect for images below the fold and in long lists. Never lazy-load your above-the-fold hero — it delays the largest contentful paint.fetchpriority="high"hints the browser to fetch a critical image (usually the hero) sooner. Use it sparingly, on the one image that matters most.
<img src="thumb-800.jpg" srcset="..." sizes="..."
loading="lazy" alt="..." width="800" height="600" />
Both are widely supported in current browsers and degrade gracefully when they aren't.
A production-ready pattern
Putting the pieces together, here's a responsive card image that switches resolution, fills a fixed-ratio box, avoids layout shift, and lazy-loads:
<img
src="card-800.jpg"
srcset="card-400.jpg 400w, card-800.jpg 800w, card-1200.jpg 1200w"
sizes="(max-width: 700px) 100vw, 350px"
alt="Descriptive alt text"
loading="lazy"
width="1200"
height="800"
/>
.card__image {
display: block;
width: 100%;
aspect-ratio: 3 / 2;
object-fit: cover;
object-position: center;
}
For the reasoning behind the breakpoints in that sizes attribute, see our guide to CSS media queries. This technique is one piece of a larger picture — the full responsive web design guide covers layout, typography, and units together, and if you want to add motion or polish once the structure is solid, browse the CSS techniques section.
Quick reference
| Goal | Tool | Where |
|---|---|---|
| Stop images overflowing | max-width: 100%; height: auto |
CSS |
| Serve the right resolution | srcset + sizes (w descriptors) |
HTML |
| Serve for fixed-size images | srcset (x descriptors) |
HTML |
| Different crop per breakpoint | <picture> + media |
HTML |
| Modern formats with fallback | <picture> + type |
HTML |
| Fill a fixed box without distortion | object-fit: cover |
CSS |
| Aim the crop | object-position |
CSS |
| Reserve space / stop layout shift | width/height or aspect-ratio |
HTML + CSS |
| Defer off-screen images | loading="lazy" |
HTML |
Responsive images aren't one feature — they're a handful of small, well-supported tools that each do one job. Ship the right bytes with srcset and sizes, keep the layout stable with aspect-ratio, and control the crop with object-fit. Do those three things and every image on your site earns its place on every screen.