Images in HTML (page 04) taught the syntax of srcset, sizes and <picture>. This page is about the reasoning around it: what CSS has to do so an image fits a fluid layout, how the page keeps still while images load, and how to get from a layout in a style sheet to the exact sizes value that describes it. Media and container queries themselves are taught on the CSS section's Responsive design page.
1. The fluid baseline
An <img> is a replaced element: the browser lays out a box for it, and the content of that box comes from outside the document, from the image file. Left alone, the box takes the image's natural size, the pixel width and height stored in the file, as a size in CSS pixels. A 1600-pixel photograph is 1600 CSS pixels wide, whatever column it sits in, and on a phone it pushes the whole page sideways.
Where this started: percentage widths
The early answer to fluid layouts, long before the term "responsive design", was to give the image a percentage in its width attribute, so it would always fill its column:
It degraded gracefully, in that the image always fit, but it had three faults. A small image in a wide column was stretched past its natural size and blurred, because the browser had to invent pixels. A large image in a narrow column was sent at full weight and scaled down, wasting the bytes. And with no height, the browser had no idea how tall the image would be until it arrived. The attribute is not even valid today: HTML defines width and height on <img> as plain non-negative integers, in CSS pixels, with no percentages.
The modern baseline
The fix moved to CSS, which is where layout decisions belong. Two declarations, applied to every image, make images fluid without making them grow:
max-inline-size: 100% lets the image shrink to fit its container but never stretch past its own size: a 480-pixel image in a 900-pixel column stays 480 pixels wide and sharp. (max-inline-size is the logical name for max-width in a horizontal writing mode; see Layout for logical properties.)
block-size: auto is the half people forget, and it is there because of the width and height attributes. Those attributes are presentational hints: the browser turns them into CSS width and height declarations, treated as author styles with zero specificity that come before every rule you write. Any CSS you write therefore overrides them, which is the improvement the old approach lacked. But the hint for height still applies unless something overrides it. Shrink the width with max-inline-size and the height stays at 533 pixels, and the photograph is squashed. block-size: auto overrides the height hint, so the height follows the width and the proportions hold.

2. Reserving space: width, height and aspect-ratio
Images arrive after the HTML. The browser lays out the text as soon as it can, and an image may follow a moment or several seconds later. If the browser does not know how big the image will be, it gives it no space; when the file arrives, the image takes its space and everything after it moves down. That movement is a layout shift.
The fix is the oldest advice in this section, and for a few years it seemed to stop working. When fluid images became the norm, the CSS overrode the attributes' width and the attributes' height was cancelled by block-size: auto, so in those years writing width and height reserved nothing. Many developers stopped writing them.
Browsers fixed this in 2019 and 2020, and the HTML standard now describes it. The width and height attributes of an <img> map to the CSS aspect-ratio property as well as to width and height. The browser's own style sheet in effect contains:
The auto means "use the image's own ratio once it has loaded; until then, use 800 / 533". So the fluid baseline and the attributes work together. CSS decides the width (the column, at most the image's own width), block-size: auto lets the height follow, and the aspect ratio from the attributes tells the browser what "follow" means before a single byte of the image has arrived. The box is the right shape from the first layout, and the image fills it when it comes.
That is why the section's second image rule says every content image has width and height. The numbers do not have to be the size it will be displayed at; they only have to have the right ratio. The convention is to use the natural size of the fallback file, which is what this site does.
Layout shift as a number: CLS
Shifts are not only ugly. A reader who is halfway through a sentence loses their place; a reader about to tap a link taps an advertisement that slid under their finger instead. Google measures this as Cumulative Layout Shift (CLS), one of the three Core Web Vitals alongside Largest Contentful Paint (LCP), which measures loading, and Interaction to Next Paint (INP), which measures responsiveness. Image performance (page 08) covers LCP.
Each unexpected shift gets a score: the fraction of the viewport the moving content touched, multiplied by the fraction of the viewport it moved. Shifts that come in a burst, less than a second apart and within five seconds overall, form a session window, and a page's CLS is the total of its worst window. Shifts within half a second of a tap, click or key press do not count, because the reader expected something to change. A CLS of 0.1 or less is good, above 0.25 is poor, and web.dev's guidance is to meet the target at the 75th percentile of real page loads, on phones and desktops separately.
When the attributes are not enough: CSS aspect-ratio
The attributes cover images whose size you know. Two cases need the CSS aspect-ratio property, which is Baseline widely available. The first is a box you want at a fixed shape whatever the file's own shape, such as a row of tiles; section 6 builds one. The second is an embedded video or map in an <iframe>, which has no natural size to derive a ratio from:
An author aspect-ratio rule beats the one derived from the attributes, for the same reason all author CSS beats presentational hints. Keep the attributes anyway: they are what the browser uses if the style sheet is slow or never arrives.
3. Resolution switching and art direction
A fluid image fits its column; it does not yet send the right file. Responsive images, the markup page 04 teaches, solve two different problems, and it matters which one you have, because they are written differently and the browser treats them differently.
| Resolution switching | Art direction | |
|---|---|---|
| The problem | The same picture is needed at different pixel sizes, for different layouts and densities | A different picture, usually a different crop, suits a different layout |
| The files | One image scaled to several widths | Different images, each possibly at several widths |
| The markup | <img srcset sizes> | <picture> with <source media> |
| Who decides | The browser, which may weigh density, network and cache | The author: the first <source> whose media matches must be used |
| If it goes wrong | A file a little too large or too soft | The wrong composition: a mountain too small to see on a phone |
Most images need only resolution switching. The browser knows the screen, the zoom level and sometimes the connection, and the author knows the layout. The srcset attribute is a list of image candidate strings, each a URL followed by a width descriptor such as 800w, meaning "this file is 800 pixels wide". The sizes attribute says how wide the image will be laid out. From those, the browser works out the pixel density each candidate would give and picks one. Because the decision is the browser's, it is allowed to choose a smaller file to save data, or reuse a larger one it already has in its cache.
Art direction is the exception, used when scaling is not enough. The lake photograph is a wide landscape; at phone width the mountain becomes a small white triangle. A square crop around the peak tells the story better on a narrow screen:

<picture> would use it below some width and the landscape above it. This figure is lazy-loaded with sizes="auto", the subject of section 5.Note what art direction costs: every crop is another set of files at several widths and in several formats, and a <source> for each combination. Use it when the composition really fails at small sizes, and use resolution switching for everything else. The same <picture> mechanism, with type instead of media, chooses formats; page 04 shows both, and its art-direction demo puts the two crops on screen.
4. A worked example of sizes, from layout to attribute
The sizes attribute is where responsive images go wrong most often, because it asks the author to describe, in a small language of its own, a layout that lives in the style sheet. The method is mechanical: read the layout, write down the image's width in each of its states, and translate. Here it is for a common layout, a grid of cards that each start with a photograph.
The layout
Each card is an <li> whose image fills its width. Read the layout as a function of the viewport width, written here as 100vw:
- Under 40rem, one column. The image is the viewport minus the two gutters:
100vw - 2rem. - From 40rem to 64rem, two columns. Take away the gutters and the one gap, then halve: (100vw − 2rem − 1.5rem) / 2, which is
50vw - 1.75rem. - From 64rem to 70rem, three columns, still growing. Take away the gutters and two gaps, then divide by three: (100vw − 2rem − 3rem) / 3, about
33.33vw - 1.67rem. - From 70rem up, the container stops growing. The content box is 70rem − 2rem = 68rem; less two gaps leaves 65rem; a third of that is a fixed
21.67rem, about 347 CSS pixels, however wide the window gets.
The attribute
Each state becomes one source size: a media condition and a length. The browser reads them in order and uses the first whose condition is true, so the widest condition goes first, and the last entry, with no condition, is the default:
Three details in that value are easy to get wrong. The lengths can be calc() expressions but not percentages, because a percentage of what? The browser has not laid out the page. A rem in sizes, as in any media condition, means the browser's default font size, usually 16 pixels, not whatever the page's style sheet sets on html; that is why the layout here uses rems and leaves the root font size alone. And 100vw includes the scrollbar where scrollbars take up space, so the value slightly overestimates the slot on desktops. That is the safe direction: a candidate a few percent too large, never too small.
What the browser picks
With the slot width known, the browser divides each candidate's width by it to get the density that candidate would deliver, and compares that with the device pixel ratio. The HTML standard leaves the final choice to the browser, but in practice browsers take the smallest candidate that meets the DPR. The table works it through for the AVIF versions of the four lake files (the <picture> in section 1 offers exactly these):
| Viewport (CSS px) | DPR | Slot (CSS px) | Pixels needed | Chosen file | Bytes |
|---|---|---|---|---|---|
| 375 | 2 | 343 | 686 | lake-800.avif | 21,823 |
| 375 | 3 | 343 | 1,029 | lake-1200.avif | 45,095 |
| 600 | 3 | 568 | 1,704 | lake-1600.avif | 74,761 |
| 800 | 1 | 372 | 372 | lake-480.avif | 8,760 |
| 800 | 2 | 372 | 744 | lake-800.avif | 21,823 |
| 1024 | 1 | 315 | 315 | lake-480.avif | 8,760 |
| 1024 | 2 | 315 | 629 | lake-800.avif | 21,823 |
| 1440 | 1 | 347 | 347 | lake-480.avif | 8,760 |
| 1440 | 2 | 347 | 693 | lake-800.avif | 21,823 |
| 1920 | 2 | 347 | 693 | lake-800.avif | 21,823 |
The pattern is worth noticing. The largest files go to the phones, because a single-column card on a dense screen needs more pixels than a third-of-a-row card on a desktop. At 600 pixels and DPR 3 no candidate is big enough, so the browser takes the largest, 1600, and scales it up slightly. And on a wide desktop the image never changes, however wide the window, because the grid stopped growing at 70rem.
Now delete the sizes attribute. With width descriptors and no sizes, the browser assumes 100vw: the image fills the window. On the 1440-pixel desktop at DPR 1 it wants 1,440 pixels and downloads lake-1600.avif, 74,761 bytes, instead of 8,760: more than eight times the data for a card that is 347 pixels wide. That is the cost of a missing or careless sizes, multiplied by every card on the page.
Why sizes can only see the viewport
The grid above changes columns with media queries, so sizes could copy them. Change the grid to a container query, or put the same card component in a narrow sidebar and a wide main column, and sizes has no way to say so. Its media conditions are about the viewport, and there is no container condition in sizes.
The reason is timing, and it is the whole point of responsive images. The browser chooses a candidate while it is still parsing the HTML, often in the preload scanner that runs ahead of the parser looking for files to fetch, before the style sheets have arrived and long before any layout exists. At that moment the only facts it has are the viewport size and the DPR. The author's sizes value is a promise about a layout the browser has not computed yet. A container's width is a result of layout, so it is not available to that early choice.
In practice there are three answers. A component that sits in containers whose widths are themselves a function of the viewport can still get a viewport sizes: work out the container's width at each breakpoint, as above. A component used in many places can get a sizes written for its widest likely slot, accepting some waste in narrow places. And an image that is lazy-loaded can let the browser measure it, which is the next section.
5. sizes="auto" with lazy loading
A lazy-loaded image is not fetched until the reader scrolls near it, by which time the page has been laid out and the image's box already has a width. For those images the timing argument above does not apply, so the HTML standard added a keyword that tells the browser to use that width:
The rules are strict. auto works only on an image with loading="lazy", and only as the first entry in sizes. When it applies, the browser uses the width of the image's laid-out box, so a container query, a sidebar or a flex item all just work. The entries after auto are a fallback: a browser that does not support the keyword skips it as an invalid entry and uses the rest, so always write a real sizes after it.
It also needs the image's dimensions. The box has to have its real width before the file arrives, which is what width, height and the fluid baseline provide. The standard warns that without dimensions the image will probably be laid out at 300 by 150 pixels, the default size for a replaced element with no other information, and the browser will choose a candidate for a 300-pixel box.
| Engine | Since |
|---|---|
| Chromium (Chrome, Edge, Opera, Samsung Internet) | Chrome 126, June 2024 |
| Gecko (Firefox) | Firefox 150, April 2026 |
| WebKit (Safari) | In the Safari 27 betas, announced at WWDC 2026; Safari 26 and earlier do not support it |
So sizes="auto" is arriving in every engine, but it is not yet Baseline, and the Safari versions still in wide use ignore it. That is harmless as long as the fallback is good: those browsers use the rest of the attribute exactly as they would have without auto. The keyword is a progressive enhancement that costs nothing to write.
6. Images in grid, flex and container layouts
The fluid baseline behaves well in normal flow. Grid and flex layouts add two surprises, and tiled galleries add a third need.
Tracks and items that will not shrink
A grid track sized 1fr has a minimum of auto, which means it will not shrink below the smallest size its contents can take. An image with the fluid baseline is not a problem here: a percentage maximum lets an image shrink to nothing, so it never forces a track wider. But an image without the baseline forces the track to the image's full natural width, and so does anything else that cannot shrink, such as a wide <pre> or a long URL in the caption beside it. The column overflows and takes the page with it.
Flex items have the same minimum, and a second habit: an item's starting size, its flex basis, is by default the size of its content. A figure holding a 1600-pixel photograph starts at 1600 pixels and shrinks only until its neighbor reaches its own minimum, so the photograph takes nearly the whole row and squeezes the text beside it into a sliver.
min-inline-size: 0 on a grid or flex item, or minmax(0, 1fr) for a grid track, removes the automatic minimum, so the item can be narrower than its content and the content's own rules (the fluid baseline, overflow-wrap, a scrolling <pre>) take over. A flex basis of 0 makes the items share the row by their grow factors instead of by their content. The card grid in section 4 used minmax(0, 1fr) for this reason. The CSS section's Layout page covers the sizing rules behind both.
Uniform tiles: aspect-ratio and object-fit
Photographs come in many shapes, and a gallery of them in a grid looks ragged. The fix is to decide the tile's shape in CSS and let each photograph fill it, cropping what does not fit:
The author aspect-ratio overrides the one from the attributes, so every tile is 4 by 3, and the space is still reserved before any file arrives. object-fit: cover scales each image to cover its box and crops the overflow, the way background-size: cover does for a background; object-position chooses which part survives the crop. Without object-fit, a portrait photograph forced into a landscape box is squashed. Images in CSS (page 05) covers both properties in full.
Cropping is a decision about content. object-fit: cover crops blindly from the position you give, so a face near the edge of a portrait can be cut off in a landscape tile. When the crop matters, make it on purpose, as the lake-peak files were, and serve it with art direction.
Containers
Container queries let a card change its own layout, image above text in a narrow slot and beside it in a wide one, whatever the viewport is. The image's CSS follows without any help: it is fluid, so it takes whatever width its part of the card gets. Only the choice of file has to be thought about, and section 4 and section 5 between them give the answer: a sizes value worked out from the viewport where the containers' widths follow the viewport, a generous one where they do not, and sizes="auto" on every lazy image so browsers that support it can measure instead of guess.
7. Summary
| Concept | Key points |
|---|---|
| The fluid baseline | max-inline-size: 100% lets images shrink but not stretch; block-size: auto overrides the height hint from the attributes so they keep their shape. Percentage width attributes are history, and invalid. |
| Reserving space | width and height map to aspect-ratio: auto w / h, so the box has the right shape before the file arrives. Use CSS aspect-ratio for fixed shapes and embeds. |
| CLS | A Core Web Vital: the worst burst of unexpected shifts, each scored as impact times distance. Good is 0.1 or less at the 75th percentile. Lazy images without dimensions are a common cause. |
| Switching or directing | Resolution switching sends one picture at the right size, with srcset and sizes, and the browser decides. Art direction sends a different crop with <picture> and media, and the author decides. |
| Writing sizes | Read the layout, write the image width in each state, list the widest condition first and a default last. Use calc(), no percentages; a rem is the default font size. Without sizes, the browser assumes 100vw. |
| The viewport limit | The browser picks a file before CSS and layout exist, so sizes can see only the viewport, never a container. |
sizes="auto" | For lazy images only, first in the list, with a real fallback after it and dimensions on the image. In Chromium and Firefox; arriving in Safari 27; not yet Baseline. |
| Grid and flex | min-inline-size: 0 or minmax(0, 1fr) lets items shrink; a flex basis of 0 shares the row. aspect-ratio with object-fit: cover makes uniform tiles. |