Images
An image is one void element and a handful of attributes, two of which are required and one of which is skipped constantly.
<img src="images/cat.jpg" alt="A grey cat asleep on a sunlit windowsill"
width="640" height="480">
Open this example in the editor and change it. Nothing you type is saved.
src is the file. alt is what the image says. Everything else is optimisation.
alt text is content, not metadata
The habit to break is thinking of alt as a label. It is a replacement: the text that takes the image's place for anybody who is not receiving the image - a screen reader user, somebody on a connection where it failed to load, and the search engine indexing the page.
So the question is not "what is in this picture?" It is "what would I write here if I could not use a picture?"
<p>
<img src="chart.png"
alt="Revenue rose each quarter, from 1.2 million in Q1 to 2.1 million in Q4.">
</p>
<p>
<a href="/profile">
<img src="avatar.jpg" alt="Your profile">
</a>
</p>
<p>
<img src="divider.svg" alt="">
A decorative flourish, which should say nothing at all.
</p>
Open this example in the editor and change it. Nothing you type is saved.
Three cases, three different answers:
An informative image gets the information. A chart's alt is the finding, not the word "chart" - a reader who cannot see it needs what it shows, and "Quarterly revenue chart" tells them only that they are missing something.
An image inside a link gets the link's destination, because the image is standing in for the link text. alt="Your profile", not alt="Photograph of a person".
A decorative image gets alt="" - present and empty. That is not the same as omitting it. An empty alt tells assistive technology to skip the image entirely; a missing alt leaves it with nothing to go on, so many screen readers read the filename instead, and hero-image-final-v3-compressed.jpg is read out, character by character.
A good rule for length: one sentence. If it needs a paragraph, the information belongs in the page where everybody can read it, with the image carrying alt="".
Width, height and why they are back
For years the advice was to leave dimensions out and let CSS handle sizing. That advice reversed.
Set width and height to the image's real pixel dimensions and the browser knows the aspect ratio before the file arrives, so it can reserve exactly the right space. Without them the space is zero until the image loads, and everything underneath jumps down when it does - the single most irritating thing on a modern web page, and one of the things search engines measure.
They do not fight your CSS. img { max-width: 100%; height: auto; } still scales the image; the attributes supply the ratio, the stylesheet supplies the size.
figure and figcaption
When an image needs a visible caption, the two belong together as one unit.
<figure>
<img src="parse-tree.png"
alt="The parser builds html, then head and body, then the elements inside each.">
<figcaption>How a browser turns markup into a document tree.</figcaption>
</figure>
Open this example in the editor and change it. Nothing you type is saved.
figure marks self-contained content that is referred to from the main text - an illustration, a chart, a code listing, a photograph - and figcaption is its caption. The association is explicit, so a screen reader announces the caption with the figure rather than leaving a stray sentence after an image.
The caption and the alt do different jobs. The caption is read by everyone and says what the figure is for; the alt is read instead of the image and says what it shows. Repeating one in the other means a screen reader user hears the same sentence twice.
Formats, and choosing one
| Format | Use it for |
|---|---|
| JPEG | Photographs. Lossy, small, no transparency. |
| PNG | Screenshots, flat colour, anything needing transparency. Lossless. |
| SVG | Logos, icons, diagrams. Vector, so it stays sharp at any size. |
| WebP | A modern replacement for both JPEG and PNG, usually 25-35% smaller. |
| AVIF | Smaller again, slower to encode, now widely supported. |
The practical advice is short: photographs as WebP or AVIF with a JPEG fallback, anything drawn rather than photographed as SVG, screenshots as PNG unless they are large enough to be worth WebP.
One image, several files
Sending a 2400-pixel photograph to a phone wastes the visitor's data and their battery. srcset lets you offer the same image at several sizes and have the browser pick.
<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="The lecture hall during an open day"
width="800" height="533">
Open this example in the editor and change it. Nothing you type is saved.
srcset lists the files with their real widths. sizes tells the browser how wide the image will be laid out - here, the full viewport below 700 pixels and 700 pixels above it. The browser combines the two with the device's pixel density and downloads one file. src stays as the fallback for anything that does not understand srcset.
When the files are different formats rather than different sizes, the element is picture:
<picture>
<source srcset="photo.avif" type="image/avif">
<source srcset="photo.webp" type="image/webp">
<img src="photo.jpg" alt="The lecture hall during an open day"
width="800" height="533">
</picture>
Open this example in the editor and change it. Nothing you type is saved.
The browser takes the first source whose type it supports and falls back to the img. Note that the img is required and carries the alt: picture is a wrapper that chooses a file, not a replacement for the image element. There is a whole lesson on this later, under responsive HTML.
Lazy loading
<img src="far-down-the-page.jpg" alt="A diagram of the box model"
width="800" height="600" loading="lazy">
Open this example in the editor and change it. Nothing you type is saved.
loading="lazy" tells the browser not to fetch the image until it is near the viewport. On a long page that is a large saving for anybody who does not scroll to the bottom.
One rule: never put it on an image that is visible when the page opens. Lazy-loading the first thing the visitor sees delays exactly the image whose speed is being measured.
When an image is not an img
An SVG can be referenced like any other file, or pasted straight into the document.
<p>Referenced, like any other image:</p>
<img src="logo.svg" alt="Omni Academy" width="160" height="40">
<p>Inlined, so CSS and script can reach inside it:</p>
<svg width="24" height="24" viewBox="0 0 24 24" role="img" aria-label="Download">
<path d="M12 3v12m0 0l-4-4m4 4l4-4M4 19h16" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round"></path>
</svg>
Open this example in the editor and change it. Nothing you type is saved.
Referenced is right for anything large or reused across pages: the browser caches one file. Inline is right for icons, because stroke="currentColor" then picks up the surrounding text colour automatically, and because an inline icon costs no request at all.
An inline svg needs role="img" and aria-label to be announced, or aria-hidden="true" if it is decorative and sits next to text that already says the same thing. There is a lesson on graphics later that goes further.
Practice task
Build a page with four images: a photograph, a chart, an image that is also a link, and a decorative divider. Write the alt for each by covering the image and asking what you would write in its place. Then set width and height on all four, throttle the network in the developer tools to a slow connection, and reload - with the dimensions present, nothing on the page should move as the images arrive.