Skip to content
pageinspection

Two attributes that stop your page jumping

A browser cannot know how much room an image needs until the file arrives, so it leaves none and everything below moves when it lands. Two attributes prevent that, and the modern reason they work is not the reason they were originally added.

The short answer

Width and height attributes on an image element let the browser reserve the right space before the file arrives, which stops the text below jumping when it loads. They still work when CSS controls the display size, because modern browsers use the two numbers as an aspect ratio rather than as fixed pixels.

RefinementAudit check · Image dimensions

The paragraph moves while somebody is reading it

Layout shift is not an aesthetic problem. A reader is on the third line of a paragraph, an image above it finishes loading, the paragraph moves down eighty pixels, and they have lost their place. On a touch screen the same shift moves a button under a finger that was already descending, which is how people tap things they did not choose.

Two versions of one page. Without declared dimensions, the paragraph starts directly under the heading and is pushed 50 pixels down the page when the image finishes loading, so a reader loses their line. With width and height declared, the space is reserved before the image arrives and the paragraph is already in its final position.The paragraph you were reading, and where it wentno width or heightimage arrives50pxwidth and height declaredspace reservednothing movesThe attributes are not about the size the image is displayed at. They givethe browser a ratio, so it can hold the right space before the file lands.
The dashed outlines are where the paragraph was before the image arrived. With dimensions declared, the browser holds the space from the first render, and the text is in its final position before the file has been requested.

The cost is measured, too. Cumulative layout shift is one of the three Core Web Vitals, and unsized images are its most common single cause. That makes this one of the few checks in an audit where the effect on a real, published metric is direct rather than argued for.

Why two attributes work when CSS controls the size

The original purpose of width and height was to state the size the image would be displayed at, and responsive design made that useless: the displayed size depends on the viewport, and CSS decides it.

What changed is that browsers now use the two attributes to compute an aspect ratio, and combine it with the CSS width to work out the height before the file arrives. So the attributes no longer declare a size, they declare a shape, and the shape is all that was needed.

images.html
<!-- the attributes give a ratio. CSS still decides the actual size -->
<img src="/img/knife.jpg" width="1200" height="800" alt="A carbon steel chef's knife" />

<style>
  /* required, or the height attribute wins and the image is distorted */
  img { max-width: 100%; height: auto; }
</style>

<!-- a responsive set: one ratio, several files -->
<img
  src="/img/knife-800.jpg"
  srcset="/img/knife-400.jpg 400w, /img/knife-800.jpg 800w, /img/knife-1600.jpg 1600w"
  sizes="(max-width: 700px) 100vw, 700px"
  width="1200" height="800"
  alt="A carbon steel chef's knife"
/>

<!-- lazy loading, which is where the attributes matter most -->
<img src="/img/knife.jpg" width="1200" height="800" loading="lazy" alt="..." />

The height: auto in that stylesheet is not optional. Without it the height attribute is applied literally alongside a percentage width, and the image is stretched. This is the reason a lot of teams removed these attributes years ago, and the reason removing them is now the wrong call.

Two details on the responsive case. The values should be the intrinsic dimensions of the file, not the size you expect it to render at, and every image in a srcset has to share the same aspect ratio or the reserved space is wrong for some of them. And with loading="lazy" the attributes become more important rather than less: a deferred image arrives later, so the shift it causes happens while somebody is definitely reading.

Where the attributes go missing

Nobody deletes them one at a time. They are absent in patterns:

  • Content-managed images. An editor pastes or uploads an image into an article body and the CMS emits an img tag with no dimensions, because it did not measure the file. This is the largest source on any site with an editorial workflow.
  • Images inserted by script. A carousel, a gallery, a lazy loader that swaps a placeholder for the real thing. The element is created at runtime and the attributes are whatever the code sets, which is usually nothing.
  • Background images in CSS. Not an img element at all, so there are no attributes to set. A container with no height reserved shifts exactly the same way, and the fix there is an aspect-ratio in the stylesheet.
  • Third-party embeds. An advert, a map, a video player, a social embed. Frequently unsized, frequently the largest shift on the page, and outside your control except by reserving space around them.

The framework answer is worth knowing: most modern image components require dimensions and emit them for you, which turns this from an ongoing discipline into a build-time guarantee. If your stack has one, using it everywhere closes this check permanently.

The images that shift and the images with no description are usually the same images.

Both attributes come from the same place: whoever inserted the image, or the template that rendered it. So a page with unsized images almost always has images with no alt text too, and both are fixed by the same change to the same component.

If you are going to touch every image tag on the site once, do the two together rather than in two passes.

Every image element has to be read

This needs the attributes on each img element on the page, and the payload behind this search reports how many images a page has rather than what each one declares. Counting images is not the same question as reading them.

This one needs the full crawl

This needs the width and height attributes on every image element, and the payload behind this search reports how many images a page has rather than what each one declares.

The instant search on this site audits a single page, so rather than show you a verdict it cannot support, this guide sends you to the place the check actually runs.

The version you can run today is in the last section, and it takes one command.

Why a measured metric is still a Refinement

Because the page works. Every image loads, the content is complete, nothing is excluded, and the finding describes a few hundred milliseconds of instability while a page assembles. That is an improvement to a working page, which is what the bottom of the scale is for.

It is worth being straightforward that the ranking argument for this is thin. Core Web Vitals are a real input and a small one, and no amount of layout stability rescues a page that does not answer the question. Any guide claiming otherwise is selling something.

What makes it worth doing anyway is the cost. Two attributes, emitted by a component, fixed once for every image on the site. There is no editorial judgement, no content to write and no risk beyond the one line of CSS in the second section. A refinement that closes permanently in an afternoon is exactly the kind of work to do while you are already in the template for something else.

Listing the images with nothing declared

One command per page, and a loop over your sitemap if you want the whole site.

unsized-images.sh
URL=https://example.com/guides/carbon-steel

# every img tag, split into those with dimensions and those without
curl -s "$URL" | tr '>' '>\n' | grep -o '<img [^>]*' \
  | while read -r tag; do
      case "$tag" in
        *width=*height=*|*height=*width=*) ;;
        *) echo "unsized: $(printf '%s' "$tag" | grep -o 'src="[^"]*"')" ;;
      esac
    done

# how many of each, as a ratio
total=$(curl -s "$URL" | grep -o '<img ' | wc -l)
sized=$(curl -s "$URL" | tr '>' '>\n' | grep -o '<img [^>]*' \
  | grep -c 'width=' || true)
printf '%s of %s images declare a width\n' "$sized" "$total"

Read the src values that come back. If they are all in one directory, or all uploads, you have found the CMS problem rather than a template problem, and the fix is at the point of insertion.

The other check no script does: load the page on a throttled connection with your browser's developer tools and watch it assemble. Anything that jumps is a shift, including the embeds and background images the command above cannot see.

Questions this check raises

Do width and height attributes conflict with responsive CSS?
No, not since browsers began deriving an aspect ratio from them. Set the attributes to the intrinsic size of the file and let CSS control the rendered width with height:auto. The browser reserves the correct box before download and then scales it, which is exactly the behaviour you want.
What is cumulative layout shift and how do images cause it?
It measures how much visible content moves after it first renders. An image with no reserved space occupies zero height until it loads, so everything below it sits higher, then jumps down when the file arrives. That jump is the shift, and it is the most common single cause of a poor score.
Do I need dimensions on images that are lazy loaded?
More so, not less. A lazy-loaded image arrives later, so the shift it causes happens while someone is already reading, which is the worst version of the problem. The attributes are what let the placeholder hold the right amount of space until it does.