Images
How to embed images, write useful alt text, and prevent layout shift.
The <img> element embeds an image into a page. Its two required attributes are src (the image path) and alt (a text description). Getting those two right is the most important part of working with images.
The alt attribute
Without alt, an image has no meaning for anyone who cannot see it.
What breaks without alt text
A screen reader is software that reads page content aloud for blind and low-vision users. When a screen reader encounters an <img> with no alt attribute, it reads the filename instead.
<!-- Screen reader says: "logo dot png" -->
<img src="logo.png">
<!-- Screen reader says: "slash assets slash uploads slash img dash 4 8 7 2 dash final dot jpg" -->
<img src="/assets/uploads/img-4872-final.jpg">Neither is useful. The user has no idea what they are looking at.
The two valid modes
The alt attribute has exactly two correct uses.
Meaningful images describe what the image communicates. Write what a sighted person learns from looking at it. Do not start with "Image of" or "Photo of" -- the screen reader already announces it as an image before reading the alt text.
<!-- Wrong: redundant prefix, no useful information -->
<img src="mug.jpg" alt="Image of a mug">
<!-- Correct: describes the content -->
<img src="mug.jpg" alt="Blue ceramic mug with white handle, 350ml capacity">
<!-- Correct: for a chart, describe the data, not the visual -->
<img src="q1-sales.png" alt="Bar chart showing Q1 sales. March had the highest sales at 1,240 units, up 23% from February.">Decorative images exist for visual effect and carry no information. Use an empty alt attribute. An empty string tells screen readers to skip the image entirely.
<!-- Empty string: screen reader skips this image -->
<img src="divider-wave.png" alt="">This is not the same as omitting alt. Missing alt means "I forgot." Empty alt means "this is intentionally decorative." Always include the attribute.
<!-- Missing alt: screen reader reads the filename (broken) -->
<img src="divider-wave.png">
<!-- Empty alt: screen reader skips it (correct for decorative) -->
<img src="divider-wave.png" alt="">Why alt text matters beyond screen readers
Alt text also displays when an image fails to load -- a broken path, a slow connection, a blocked image. Good alt text means the user still gets the information. It also helps search engines understand what an image depicts.
Width and height
Without explicit dimensions, the browser does not know how big an image is until it downloads. The page renders first without the image, then shifts when the image arrives. Text jumps down. Buttons move. Users lose their place.
This is called Cumulative Layout Shift (CLS). It is a real usability problem and a metric search engines use to evaluate page quality.
What breaks without dimensions
<!-- Browser reserves no space. Everything below the image shifts when it loads. -->
<img src="product.jpg" alt="Wireless headphones in matte black">
<p>These headphones ship in 2–3 business days.</p>On a slow connection, the paragraph sits at the top of the page while the image loads, then jumps down. If the user has already started reading or clicked a link, the shift disrupts them.
Setting width and height
Add width and height in pixels. The browser uses these to reserve the correct amount of space before the image downloads.
<img src="product.jpg" alt="Wireless headphones in matte black" width="800" height="600">You do not need the image to display at exactly those pixel dimensions. CSS takes over from here. The values establish the aspect ratio so the browser can calculate how tall to make the reserved space before the image arrives.
img {
max-width: 100%;
height: auto;
}With max-width: 100% and height: auto, the image scales to fit its container while the browser still has the ratio it needs to pre-allocate space correctly.
Lazy loading
By default, the browser begins downloading every <img> on the page immediately. On a long page with ten images, all ten start downloading at once. A user who only scrolls halfway down has wasted bandwidth downloading the bottom five.
The loading="lazy" attribute defers an image download until the image is close to entering the viewport.
<img src="behind-the-scenes.jpg" alt="The design team reviewing prototypes" width="1200" height="800" loading="lazy">Use loading="lazy" on images below the visible area on page load. Do not use it on images in the viewport when the page first appears -- a hero image, a product photo at the top of a listing. Lazy loading those delays the most important images and slows down the perceived load time.
<!-- Above the fold: load immediately (default, no attribute needed) -->
<img src="hero.jpg" alt="Our new collection for spring" width="1600" height="900">
<!-- Below the fold: defer until the user scrolls toward it -->
<img src="lookbook-1.jpg" alt="Model wearing the linen shirt in sage green" width="800" height="1000" loading="lazy">
<img src="lookbook-2.jpg" alt="Close-up of the embroidered hem detail" width="800" height="1000" loading="lazy">Responsive images with srcset and sizes
One image file served to every device creates a problem in two directions. Mobile users download a full 1600px desktop image and waste bandwidth. If you serve a small image optimised for mobile, desktop users see a blurry stretched photo.
srcset and sizes solve this by giving the browser a menu of image files and letting it pick the most appropriate one.
srcset: the list of available files
srcset lists image files with their intrinsic widths. The w suffix means the actual pixel width of that image file.
<img
src="photo-800w.jpg"
srcset="photo-400w.jpg 400w, photo-800w.jpg 800w, photo-1600w.jpg 1600w"
alt="Two baristas preparing coffee at a wooden counter"
width="800"
height="533"
>The src attribute is still required. It is the fallback for browsers that do not understand srcset.
sizes: how wide the image will render
Without sizes, the browser assumes the image will be displayed at the full viewport width. That assumption is wrong for most images, which are constrained by a container.
sizes tells the browser how wide the image will actually be rendered. It takes a list of media conditions and sizes, evaluated left to right.
<img
src="photo-800w.jpg"
srcset="photo-400w.jpg 400w, photo-800w.jpg 800w, photo-1600w.jpg 1600w"
sizes="(max-width: 600px) 400px, 800px"
alt="Two baristas preparing coffee at a wooden counter"
width="800"
height="533"
>Reading sizes left to right: if the viewport is 600px wide or less, render the image at 400px. Otherwise render it at 800px. The browser uses this information to decide which srcset entry to download.
The browser makes this decision before downloading anything. That is why you need sizes -- without it, the browser cannot make an informed choice.
The <picture> element
srcset handles different resolutions of the same image. <picture> handles cases where you want a completely different image at different sizes -- different crops, different aspect ratios, or different file formats.
Art direction: different crops for different screens
A wide landscape photo works on a desktop. The same photo on a phone at landscape-cropped may shrink faces to the point of unrecognisability. <picture> lets you swap in a tighter crop.
<picture>
<source srcset="team-wide.jpg" media="(min-width: 900px)">
<source srcset="team-square.jpg" media="(min-width: 500px)">
<img src="team-portrait.jpg" alt="The founding team of five people at a rooftop event" width="600" height="800">
</picture>The browser tries each <source> in order and uses the first one whose media condition matches. If none match, it falls back to the <img>.
The <img> element inside <picture> is not optional. It is the fallback for browsers that do not support <picture>, and it is where you put the alt text and dimensions. <source> elements do not accept alt.
Modern formats with format fallback
WebP images are typically 25 to 35% smaller than equivalent JPEGs. AVIF is smaller still. Not all browsers support both. <picture> lets you serve the best format the browser can handle.
<picture>
<source srcset="product.avif" type="image/avif">
<source srcset="product.webp" type="image/webp">
<img src="product.jpg" alt="Noise-cancelling headphones folded flat in a white case" width="600" height="600">
</picture>Browsers that support AVIF use it. Browsers that support WebP but not AVIF use WebP. Browsers that support neither fall back to JPEG.
Common mistakes
Missing alt attribute. Not the same as an empty alt. A missing attribute leaves the browser with no guidance. Screen readers announce the filename. Always include alt, even if the value is an empty string.
Writing "Image of..." in alt text. Screen readers prefix their announcement with "image" already. Your alt text begins playing right after that prefix, so starting with "Image of a coffee cup" results in "image, image of a coffee cup."
Missing width and height. The layout shifts when images load. Set both attributes on every <img> that is part of the page layout.
Using loading="lazy" on hero images. Deferring a visible image delays the first meaningful paint. Reserve loading="lazy" for images below the fold.
Omitting the <img> fallback inside <picture>. The <img> inside <picture> is required. It is the actual image element. <source> elements only provide candidate sources. A <picture> without a final <img> renders nothing.
Using identical alt and figcaption text. When an image is inside a <figure> with a <figcaption>, avoid duplicating the same text in both. The figcaption is visible to all users. The alt only needs to provide what the figcaption does not.
Exercise
Build a page with three images to practice the different techniques.
- Add a meaningful product image using
https://placehold.co/600x400as the source. Give it descriptive alt text, correctwidthandheightattributes, andloading="lazy". - Add a decorative separator image using
https://placehold.co/800x20with an empty alt attribute. - Add a
<picture>element with two<source>elements using different dimensions from placehold.co (https://placehold.co/900x400for wide screens andhttps://placehold.co/400x400for narrow screens), with appropriatemediaconditions and a fallback<img>with alt text.