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:

  • download asks 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.
  • rel describes the relation to the target, such as rel="noopener" (the new page gets no handle on this one) or rel="nofollow" for search engines. Current browsers already treat target="_blank" links as noopener.

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 alt out altogether. An empty alt="" 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">
  • srcset lists the available files and their real widths: the image is available 400, 800 and 1600 pixels wide.
  • sizes tells 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.
  • src is the fallback for very old browsers, and width/height still 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>
  • controls shows 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.
  • poster is 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="&lt;p style='font-family: sans-serif'&gt;No outages in 3011 right now.&lt;/p&gt;"
        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 title that 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.
  • sandbox takes away most powers of the framed page (scripts, forms, pop-ups), with keywords such as allow-scripts to give some back.
  • Many sites refuse to be framed at all, with a Content Security Policy frame-ancestors rule or the older X-Frame-Options header. 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.