The img deep dive
Every img attribute that earns its place — alt, width, height, loading, decoding, fetchpriority — and the layout-shift bug they prevent.
The <img> tag looks simple — src and you are done. But every other attribute on it pulls real weight. The right alt text decides what blind users hear; the right width and height prevent the page from jumping around as it loads; the right loading value saves bandwidth; the right fetchpriority makes your hero image arrive a half-second sooner.
This lesson is the per-attribute walkthrough. By the end, you will write <img> tags with five attributes and feel like every one is doing a job.
alt text decisions
alt is the text shown when the image fails to load and the text screen readers announce. It is not optional. It is not a caption. It is a description of the image's role in the page.
Three rules for writing good alt text:
-
Describe the image's content if the content matters. "A round sourdough loaf, sliced open on a wooden board." A reader who cannot see the image gets the picture.
-
Describe the image's function if it is part of an interactive element. A magnifying-glass icon inside a search button: alt = "Search", not "magnifying glass icon". The function is what matters; the visual is incidental.
-
Use empty
alt=""for purely decorative images. A flourish, a divider line, a hero pattern — anything that adds no information. Empty alt tells screen readers "skip me".
<!-- Content image: describe what's there --> <img src="loaf.jpg" alt="A round sourdough loaf, scored across the top"> <!-- Functional image: describe what it does --> <button> <img src="search.svg" alt="Search"> </button> <!-- Decorative image: empty alt --> <img src="divider.svg" alt="">
The mistake that catches everyone: leaving alt off entirely. Without alt, the screen reader falls back to announcing the filename — "image: IMG_4592.jpg". Empty alt="" is the right "no description" — it explicitly says "this is decorative, skip it".
A useful test: read the page out loud, replacing each image with its alt text. Does it still flow? If yes, the alts are right. If you hit an awkward "image, image, image", the alts need work. If a sentence makes no sense without the image's content, the alt is too short.
Don't start alt with "Image of" or "Photo of". Screen readers already announce the role ("image"). "Image of: image of a sourdough loaf" reads twice. Drop the prefix and start with the description itself.
<button><img src="trash.svg" alt="???"></button> for deleting an item. What's the right alt text?width and height stop layout shift
When the browser starts rendering the page, it does not know how big each image will be — until each image actually loads. Without help, the layout reflows the moment each image arrives, pushing other content around. That jumpy reflow is CLS (Cumulative Layout Shift) — a Core Web Vital that Google penalizes.
The fix is to declare the image's width and height attributes. The browser uses them to reserve the right amount of space before the bytes arrive.
<img src="loaf.jpg" alt="A round sourdough loaf" width="800" height="600">
Two things to know about these attributes:
-
The values are pixels, with no unit. Always. The CSS will resize the image; the attributes give the browser the aspect ratio (800 / 600) so it can scale correctly.
-
The browser converts them to an
aspect-ratioCSS rule under the hood. If your CSS setswidth: 100%(a common responsive pattern), the height auto-scales using the declared aspect ratio. The image grows and shrinks; the layout stays solid.
img {
width: 100%; /* take full container width */
height: auto; /* scale height proportionally — uses the aspect ratio */
}The combo "declare width and height; CSS sets width:100% height:auto" gives you reflow-proof responsive images. The browser reserves the right slot before any bytes arrive; the image fills it.
Without the attributes, the slot is zero pixels tall until the image loads, then the page jumps. The fix is one extra pair of attributes per image.
width or height attributes. The page renders text first, then the image arrives and pushes the text down. What is this called and what's the fix?loading and decoding
Two attributes that hint to the browser when to fetch and process the image.
loading="lazy" — defer the fetch until the image is near the viewport. For images below the fold (off-screen on first render), this saves the user from downloading things they may never see.
loading="eager" — the default. Fetch immediately.
<!-- Above the fold: keep the default eager. --> <img src="hero.jpg" alt="..." width="1600" height="900"> <!-- Below the fold: lazy. --> <img src="card-1.jpg" alt="..." width="400" height="300" loading="lazy"> <img src="card-2.jpg" alt="..." width="400" height="300" loading="lazy">
The rule of thumb: any image you can see when the page first renders should be eager. Everything below the fold can be lazy. Lazy-loading the hero image delays the LCP (Largest Contentful Paint) and hurts the user experience.
decoding="async" — decode the image off the main thread, so the browser can paint other content while the JPEG/WebP/AVIF is being decompressed. Set this on every image.
decoding="sync" — block paint until decoded. Almost never wanted.
decoding="auto" — the default; let the browser decide.
<img src="loaf.jpg" alt="..." width="800" height="600" decoding="async">
decoding="async" is a "set it and forget it" optimization — almost zero cost, small consistent benefit. Many teams add it to a default <img> template.
Don't lazy-load the hero/LCP image. loading="lazy" on the largest image above the fold delays it and hurts your Core Web Vitals score. Save lazy for cards, gallery thumbs, and other below-fold imagery.
fetchpriority for the LCP image
fetchpriority="high" tells the browser to fetch this resource ahead of others. Use it on the single most important image — usually the LCP image (the largest visible image when the page first renders).
<img src="hero.jpg" alt="A baker pulling sourdough out of the oven" width="1600" height="900" fetchpriority="high">
The browser already prioritizes images it knows are above the fold. fetchpriority="high" reinforces that hint, and helps in cases where the browser cannot tell (the image is loaded by JS, deeply nested, or below other heavy resources in source order).
fetchpriority="low" does the opposite — fetch this image last, after everything else. Use for images that are way off-screen or genuinely optional.
The combination that gets used in production over and over:
<img src="hero.jpg" alt="The bakery storefront at sunrise" width="1600" height="900" fetchpriority="high" decoding="async">
Five attributes; each one earning its place. src and alt say what the image is. width and height reserve space. fetchpriority="high" makes it the priority download. decoding="async" keeps the main thread free.
For below-the-fold images, swap to loading="lazy" and drop the fetchpriority="high". That is the entire performance toolkit for <img> in one paragraph.
loading="lazy" to it. What's the most likely effect?<img src="loaf.jpg" width="800" height="600"> and your CSS says img { width: 100%; height: auto }. The image is in a 400px wide container. What size does it render at, and is the layout stable?