Links, Images and Media
Connecting and illustrating pages: links with relative and absolute URLs, images with good alt text and responsive sizes, figures, video and audio, and lazy loading.
Links
The anchor element,
<a>, creates a
hyperlink
when it has an href attribute: the URL of the resource
it leads to. The content between the tags is the clickable link
text.
<p>Check the <a href="#">outage map</a> for your area.</p>
<p>Questions? <a href="mailto:support@example.com">Email customer service</a>
or call <a href="tel:+318001234567">0800 123 4567</a>.</p>
<p><a href="#">Annual statement 2025 (PDF, 220 KB)</a></p>
The href can hold any URL: a web page, a file, a
mailto: address that opens an email program, or a
tel: number that a phone can dial. (In the preview the
page links point to #, "this page", and the sandboxed
frame blocks the email and phone links, so nothing opens.) Other
useful attributes:
-
downloadasks the browser to save the file instead of opening it, optionally under a new name. It only works for files from the same origin. -
target="_blank"opens the link in a new tab. Use it sparingly; people can open a new tab themselves, and an unexpected one is disorienting, especially for screen reader users. When you do use it, say so in the link text. -
reldescribes the relation to the target, such asrel="noopener"(the new page gets no handle on this one) orrel="nofollow"for search engines. Current browsers already treattarget="_blank"links asnoopener.
Link text that makes sense on its own
Screen reader users often pull up a list of all links on a page, and sighted readers scan for underlined words. Both see the link text without its sentence. "Click here", "read more" and a bare URL say nothing out of context:
<!-- Unclear out of context -->
<p>To see current outages, <a href="/outages">click here</a>.</p>
<!-- Clear -->
<p>See the <a href="/outages">current outages in your area</a>.</p>
Mention the file type and size for downloads, as in the example above, and avoid links with no text at all, such as an icon-only link without a text alternative. WebAIM's 2026 survey of a million home pages found empty links on 46.3% of them.
Linking to a place on a page
A URL can end in a
fragment: # followed by the id of an element. The
browser then scrolls to that element. This is how the table of
contents of this page works.
<a href="#gas">Jump to gas tariffs</a>
…
<h2 id="gas">Gas tariffs</h2>
<a href="/help/faq.html#meter-readings">FAQ: meter readings</a>
The second link opens another page and then jumps to the element
with id="meter-readings" on it. If someone renames that
id, the link still opens the page, just at the top. Treat ids that
others link to as part of the page's public interface.
Relative and absolute URLs
An absolute URL is a complete
address that includes the scheme and host, such as
https://portal.example.com/outages/map.html; it points
to the same place from anywhere. A
relative URL leaves out the
first part and is completed by the browser from the address of the
current page (its base URL). The parts of a URL are
explained in
URLs, Encoding and Content Types.
Suppose a page on Kestrel's portal lives at
https://portal.example.com/account/readings.html. This
is how the browser resolves links on that page:
| Written in href or src | Becomes | Rule |
|---|---|---|
history.html |
https://portal.example.com/account/history.html
|
Same folder as the current page |
images/meter.jpg |
https://portal.example.com/account/images/meter.jpg
|
A subfolder of the current folder |
../outages/map.html |
https://portal.example.com/outages/map.html
|
../ goes one folder up |
/css/portal.css |
https://portal.example.com/css/portal.css
|
A leading / starts at the root of the site
|
#electricity |
https://portal.example.com/account/readings.html#electricity
|
The same page, at another place |
//cdn.example.net/logo.svg |
https://cdn.example.net/logo.svg |
Another host, same scheme (rarely needed; write
https:// instead)
|
Which kind to use:
- Links to other sites are always absolute.
- Links within your own site are usually relative. The site then works on a test server, under another domain or from a folder on your laptop without changes.
-
Root-relative URLs (
/css/portal.css) are convenient on a real web server, because they work from any folder depth. But opened straight from disk (file://), the "root" is the root of the drive, and the links break. Folder-relative URLs (../css/portal.css) work in both situations.
Images
The <img> element, a void element, embeds an
image. src is the image's URL and alt its
text alternative. width and height give
its size in CSS pixels:
<p>
<img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 160 40'%3E%3Crect width='160' height='40' fill='darkslategray'/%3E%3Ctext x='12' y='26' font-family='sans-serif' font-size='16' fill='white'%3EKestrel Energy%3C/text%3E%3C/svg%3E"
alt="Kestrel Energy" width="160" height="40">
</p>
(The image here is a tiny SVG written into the
src as a data: URL, so the example needs
no separate file. Normally src is just a path such as
images/logo.svg.)
Always set width and height to the image's
real proportions, even when CSS later scales it with
max-width: 100%; height: auto;. From the two numbers
the browser knows the aspect ratio and reserves the right space
before the file arrives. Without them, the text first renders, then
jumps down when the image loads: the layout shift that the
Core Web Vitals
measure, and the reason people tap the wrong button on a slow phone.
The common formats: JPEG for photos, PNG for screenshots and graphics with sharp edges or transparency, SVG for logos, icons and diagrams (a vector format that stays sharp at any size), and WebP and AVIF, newer formats that compress better and are supported by current browsers. Choosing and compressing them is part of Building and Publishing a Page.
Writing good alt text
Alt text is the text alternative of
an image, given in its alt attribute. Screen readers
read it aloud, browsers show it when the image fails to load, and
search engines use it to understand the image. WCAG, the
accessibility guidelines, require a text alternative for every
meaningful image (success criterion 1.1.1, level A), yet WebAIM's
2026 survey still found images without alt text on 53.1% of home
pages.
Good alt text does the job of the image in this place. Ask what a reader would miss if the image were gone:
| Kind of image | Good alt |
|---|---|
| Informative: a photo showing where the meter is |
alt="Meter cupboard under the stairs, with the gas meter
on the left"
|
| Functional: the logo as a link to the home page |
alt="Kestrel Energy home": describe where it
goes, not what it looks like
|
| Image of text: a banner saying "Fixed prices until 2027" | alt="Fixed prices until 2027" |
| Complex: a chart of monthly use | A short alt with the main point, plus the data in text or a table nearby |
| Decorative: a swoosh, a stock photo next to a heading | alt="": empty, so screen readers skip it |
- Do not start with "image of" or "picture of"; screen readers already announce that it is an image.
-
Do not leave
altout altogether. An emptyalt=""means "decorative, skip it"; a missing alt makes many screen readers read the file name instead, such as "IMG underscore 4402 dot jpg". - Do not repeat the caption or the surrounding text word for word; the reader would hear it twice.
More on text alternatives and the guidelines behind them is in Accessibility Basics.
Responsive images
A photo that fills a phone screen of 360 CSS pixels may need 1080 real pixels on a phone with a high pixel density, while a desktop column of 640 CSS pixels on an ordinary monitor needs only 640. One file cannot be ideal for both: too small looks blurry, too large wastes data and slows the page.
A responsive image is an
image offered in several files or versions, so that the browser can
load the one that best fits the reader's screen and layout. The main
tool is the
srcset attribute: a
comma-separated list of image files, each with its width in pixels
(w) or a pixel density (x).
<img src="crew-800.jpg"
srcset="crew-400.jpg 400w, crew-800.jpg 800w, crew-1600.jpg 1600w"
sizes="(max-width: 40rem) 100vw, 40rem"
alt="Two engineers repairing an underground cable at night"
width="800" height="533">
-
srcsetlists the available files and their real widths: the image is available 400, 800 and 1600 pixels wide. -
sizestells the browser how wide the image will be displayed, before any CSS has loaded: the full viewport width (100vw) on screens up to 40rem wide, otherwise 40rem. -
srcis the fallback for very old browsers, andwidth/heightstill give the aspect ratio.
The browser does the arithmetic. On a phone 360 CSS pixels wide with
a pixel density of 3, the image is displayed at 360 CSS pixels,
which needs 360 × 3 = 1080 real pixels: it picks
crew-1600.jpg. On a desktop with density 1 and a 640px
column it needs 640 pixels and picks crew-800.jpg.
Browsers may also take other factors into account, such as a
data-saving mode, so treat srcset as an offer, not an
order.
For an image that always has the same display size, such as a logo, density descriptors are simpler:
<img src="logo-160.png" srcset="logo-160.png 1x, logo-320.png 2x"
alt="Kestrel Energy" width="160" height="40">
The picture element
<picture> wraps an <img> with
one or more <source> elements. The browser uses
the first source that matches and falls back on the
img. Two typical uses:
<!-- 1. Newer formats, with JPEG as the fallback -->
<picture>
<source srcset="meter-cupboard.avif" type="image/avif">
<source srcset="meter-cupboard.webp" type="image/webp">
<img src="meter-cupboard.jpg" alt="Meter cupboard with the gas meter on the left"
width="600" height="400">
</picture>
<!-- 2. Art direction: a tighter crop on narrow screens -->
<picture>
<source media="(max-width: 40rem)" srcset="outage-map-cropped.png">
<img src="outage-map-wide.png" alt="Outage map: two areas affected in Rotterdam-North"
width="1200" height="600">
</picture>
The alt, width and
height always go on the img; the
source elements only offer files. srcset,
sizes and <picture> have been
Baseline widely available for years. A newer value,
sizes="auto", which lets the browser work out the size
of lazily loaded images itself, had limited availability as of 2026
(Chrome and Firefox only).
Figures, captions and inline SVG
<figure> groups self-contained content, such as
an image, chart, diagram, code listing or quote, with an optional
<figcaption>. The caption belongs to the figure,
so screen readers can announce them together. A figure can be moved
elsewhere (to an appendix, say) without breaking the text around it.
The chart in this example is inline SVG: the
<svg> code sits directly in the HTML instead of
in a separate file. SVG (Scalable Vector Graphics) describes shapes
and text, so it stays sharp at any zoom level, can be styled with
CSS, and its text can be read by assistive technology.
<figure>
<svg viewBox="0 0 300 120" width="300" height="120" role="img" aria-labelledby="use-title">
<title id="use-title">Electricity use: July 180 kWh, August 165 kWh, September 210 kWh</title>
<rect x="30" y="28" width="60" height="72" fill="steelblue"></rect>
<rect x="120" y="34" width="60" height="66" fill="steelblue"></rect>
<rect x="210" y="16" width="60" height="84" fill="steelblue"></rect>
<text x="60" y="22" font-size="12" text-anchor="middle">180</text>
<text x="150" y="28" font-size="12" text-anchor="middle">165</text>
<text x="240" y="10" font-size="12" text-anchor="middle">210</text>
<text x="60" y="116" font-size="12" text-anchor="middle">Jul</text>
<text x="150" y="116" font-size="12" text-anchor="middle">Aug</text>
<text x="240" y="116" font-size="12" text-anchor="middle">Sep</text>
</svg>
<figcaption>Figure 1. Electricity use per month, in kWh.</figcaption>
</figure>
role="img" with a <title> (linked
through aria-labelledby) gives the whole graphic one
text alternative, just like alt on an image. The title
here contains the data, so nobody misses the numbers. For an SVG in
a separate file, use an ordinary
<img src="chart.svg" alt="…">.
Video and audio
<video> and <audio> play media
in the page, with the browser's own controls. No plug-in is needed.
<video controls width="640" height="360" preload="metadata"
poster="reading-your-meter.jpg">
<source src="reading-your-meter.webm" type="video/webm">
<source src="reading-your-meter.mp4" type="video/mp4">
<track kind="captions" src="reading-your-meter.en.vtt" srclang="en" label="English" default>
<p>Your browser cannot play this video.
<a href="reading-your-meter.mp4">Download the video</a> instead.</p>
</video>
<audio controls src="outage-update-2026-10-12.mp3"></audio>
-
controlsshows play, pause, volume and full screen. Without it the reader cannot start the video (unless a script does). -
Several
<source>elements offer the same media in different formats; the browser plays the first one it supports. The content at the end is shown only by browsers that cannot play video at all. -
posteris the still image shown before playback;preload="metadata"fetches only the length and size, not the whole file, until the reader presses play. -
<track>adds captions or subtitles in WebVTT format (a small text file with timestamps). WCAG requires captions for prerecorded video with sound (success criterion 1.2.2, level A), and many people watch with the sound off anyway.
autoplay exists. Most browsers block autoplay with
sound (unless the reader has already interacted with the site) but
allow it when the media is muted. Besides, moving or
noisy content that starts by itself is an accessibility problem. Let
the reader press play.
Embedding other pages: iframe
An <iframe> (inline frame) shows a complete,
separate HTML document inside a box on the page. Embedded maps,
videos from a video platform and payment forms are usually iframes.
The live results on this site are iframes too.
<iframe title="Outage notice for postal code 3011"
srcdoc="<p style='font-family: sans-serif'>No outages in 3011 right now.</p>"
width="320" height="80"></iframe>
Normally the frame's address goes in src, for example
src="https://maps.example.com/embed?area=3011"; this example uses srcdoc, which holds the HTML of
the framed document itself, because the live result cannot load
other sites. Points to remember:
-
Give every iframe a
titlethat describes its content. Screen readers announce it when entering the frame. - The framed page keeps its own origin: under the same-origin policy, a page from another site cannot read or change yours, and the other way around.
-
sandboxtakes away most powers of the framed page (scripts, forms, pop-ups), with keywords such asallow-scriptsto give some back. -
Many sites refuse to be framed at all, with a
Content Security Policy
frame-ancestorsrule or the olderX-Frame-Optionsheader. That protects their users against clickjacking, where an invisible frame tricks them into clicking. An iframe that stays empty or shows "refused to connect" usually means exactly this.
Lazy loading
Lazy loading means postponing the download of a resource until it is about to be needed. For images and iframes HTML has it built in:
<img src="team-photo.jpg" alt="The customer service team in the Rotterdam office"
width="800" height="450" loading="lazy">
<iframe src="https://maps.example.com/embed?area=3011" title="Outage map for 3011"
width="600" height="400" loading="lazy"></iframe>
With loading="lazy" the browser fetches the file only
when the reader scrolls near it. A long page with dozens of images
then loads faster and uses less data, and readers who never scroll
down never download the bottom images. The attribute has been
Baseline widely available since June 2026, for both images and
iframes; the default, loading="eager", loads at once.