TH The Sass Way
Responsive Design

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

Responsive Images in CSS: srcset, sizes, and object-fit
tldrResponsive images in CSS require two jobs working together: the HTML srcset and sizes attributes tell the browser to download an appropriately sized file for each screen, while CSS properties like max-width: 100%, object-fit, and aspect-ratio control how that file displays. srcset saves bandwidth by shipping smaller files to smaller screens; CSS keeps the image fluid and prevents distortion. You need both.

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 descriptor800w 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

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:

<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.

FAQ

What is the difference between srcset and sizes?

srcset lists the image files you have and their real pixel widths (for example photo-800.jpg 800w), giving the browser a menu of resolutions. sizes tells the browser how much layout space the image will occupy at each breakpoint (for example (max-width: 700px) 100vw, 700px). The browser combines both plus the device pixel ratio to download the smallest file that still looks sharp.

What does object-fit: cover do?

object-fit: cover scales an image to completely fill its box while preserving the image's aspect ratio, cropping whatever overflows. It prevents the stretching you get from the default fill value, so portrait and landscape photos both sit cleanly in the same fixed-size slot. It is the go-to choice for uniform card grids and avatars. Pair it with object-position to control which part gets cropped.

How do I make images responsive with just CSS?

Add img { max-width: 100%; height: auto; } to keep images from overflowing their container and scaling proportionally. That makes them fluid. For true responsiveness you also want the HTML srcset and sizes attributes so smaller screens download smaller files. CSS alone shrinks one large file in the viewport; srcset actually sends fewer bytes to small devices.

When should I use the picture element instead of srcset?

Use srcset on a plain img when you are serving different resolutions of the same image. Use the picture element with source and media when you need a genuinely different crop or image at different breakpoints (art direction), or when serving modern formats like AVIF and WebP with a JPEG fallback via the type attribute. The nested img element remains required as the fallback.

Do I still need width and height on responsive images?

Yes. Setting width and height on the img lets the browser compute the aspect ratio and reserve space before the image loads, preventing cumulative layout shift where content jumps as pictures pop in. Your CSS max-width: 100%; height: auto still controls actual display size. Alternatively set aspect-ratio in CSS. Either way, reserving space is the easiest layout-shift fix available.

What descriptor should I use, w or x?

Use w descriptors (like 800w) with a sizes attribute when the image width changes across viewports, such as heroes, article images, and cards. Use x descriptors (like 2x) with no sizes when the image is always rendered at a fixed CSS size, such as avatars, icons, and logos. Never mix w and x in the same srcset, or the browser ignores the malformed entries.