# Banner and image links with no name

Source: https://easeweb.dev/learn/empty-links
Topics: Buttons and links > Links say where they go (WCAG 2.4.4)
The fix: Give the banner's link a name that says where it goes, starting with the words on its pill. Either put those words into the empty link, as visually hidden text or with aria-labelledby, or make the pill itself the link and stretch its click area over the banner with a pseudo-element. Both pass; the first changes less, the second reads more cleanly.
Test with: keyboard, screen reader, speech input, mouse
References: https://www.w3.org/WAI/WCAG22/Understanding/link-purpose-in-context.html https://www.w3.org/WAI/WCAG22/Techniques/failures/F89 https://www.w3.org/WAI/WCAG22/Techniques/html/H30 https://www.w3.org/WAI/WCAG22/Techniques/aria/ARIA7 https://www.w3.org/WAI/WCAG22/Understanding/label-in-name.html https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/a#accessibility

This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.

## What happens

A coffee roaster's home page opens with a promo banner: a photo of beans, the headline "Autumn roasts are here", a line about the new beans and a white "Shop the roasts" pill. The pointer turns to a hand anywhere on the banner, the pill fills on hover, and a click anywhere leads to the autumn collection. Pressing Tab puts focus on the banner, and a screen reader announces "link", or reads out the address, "collections/autumn-roasts". Someone who opens the screen reader's list of links finds an entry with no words, or only the address, and a voice control user who says "click Shop the roasts" gets no answer.

## Why it fails

To make the whole banner clickable, the theme wraps the banner's photo in the link and stretches it over the banner, with the headline, the text and the pill laid on top. The photo is decoration, so it has `alt=""`, and the link holds nothing else: it has no text and therefore no accessible name. With no `alt` at all, some screen readers read the image's file name instead, which is no better. Another common build reaches the same place by another route: the photo stays outside, and an empty `<a>` is laid over the banner with absolute positioning. Either way, the words sit in a sibling element, outside the link, and the pill looks like a button but is only drawn, a `span` that lets clicks through to the link under it. The browser makes the box clickable and focusable all the same, so the banner works for a mouse and looks finished.

WCAG 2.4.4 Link Purpose (In Context) asks that a link's purpose can be worked out from its text, alone or with context that software can tie to it, such as the sentence, paragraph or list item it sits in. The banner link has no text, and the headline and pill aren't its context in code: they are in a neighbouring element, not one that holds the link. So a screen reader has nothing to say but "link" or the address, and the person can't tell that it leads to the autumn roasts. An empty link fails whatever surrounds it. Links that have words, but the same vague ones on every card, are a different failure, covered in ["Read more" links that all say the same thing](/learn/read-more).

## Who is affected

Links are often read out of context, in a list or one at a time, so each one needs words of its own. Banners like this one sit at the top of home pages and campaign pages, where the link is often the main way into a sale.

## What automation and AI miss

An automated check such as axe's `link-name` reliably flags the empty banner link. It does not judge whether a name is useful, so it passes a link named "IMG_2041.jpg", or an icon whose alt is "logo". A quick AI fix can add `aria-label="Link"` or `aria-label="Banner"`, which silences the scanner and helps nobody, and leaves voice control users still unable to reach it by saying the words on the pill. A name only helps when a person reads it in a list and still knows where it goes.

## Before: the photo as the link, marked as decoration

```html
<div class="banner">
  <a href="/collections/autumn-roasts" class="banner-link">
    <img src="/images/autumn-roasts.jpg" alt="">
  </a>
  <div class="banner-text">
    <h2>Autumn roasts are here</h2>
    <p>Three new single-origin beans, roasted this week.</p>
    <span class="banner-cta">Shop the roasts<span aria-hidden="true"> →</span></span>
  </div>
</div>
```

```css
.banner {
  position: relative;
}
/* The photo fills the banner, and the link covers it. */
.banner img,
.banner-link {
  position: absolute;
  inset: 0;
}
.banner img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* The words sit over the photo and let clicks through to the link. */
.banner-text {
  position: relative;
  pointer-events: none;
}
.banner:hover .banner-cta {
  background: #fff;
  color: #2b1a10;
}
```

The link's only content is an image with `alt=""`, so it has no name, and it fails 2.4.4. The pill that says where it goes is a drawing over the link, not part of it.

## Before: the same banner, with an empty link laid over it

```html
<div class="banner">
  <img src="/images/autumn-roasts.jpg" alt="">
  <a href="/collections/autumn-roasts" class="banner-link"></a>
  <div class="banner-text">
    <h2>Autumn roasts are here</h2>
    <p>Three new single-origin beans, roasted this week.</p>
    <span class="banner-cta">Shop the roasts<span aria-hidden="true"> →</span></span>
  </div>
</div>
```

```css
.banner {
  position: relative;
}
/* The photo fills the banner, and the link covers it. */
.banner img,
.banner-link {
  position: absolute;
  inset: 0;
}
.banner img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* The words sit over the photo and let clicks through to the link. */
.banner-text {
  position: relative;
  pointer-events: none;
}
.banner:hover .banner-cta {
  background: #fff;
  color: #2b1a10;
}
```

The only change is where the photo sits: outside the link, which is now an empty box over the banner. It looks and works the same, and it fails 2.4.4 the same way.

## After: add the pill's words to the link you have

```html
<div class="banner">
  <a href="/collections/autumn-roasts" class="banner-link">
    <img src="/images/autumn-roasts.jpg" alt="">
    <span class="visually-hidden">Shop the roasts</span>
  </a>
  <div class="banner-text">
    <h2>Autumn roasts are here</h2>
    <p>Three new single-origin beans, roasted this week.</p>
    <span class="banner-cta">Shop the roasts<span aria-hidden="true"> →</span></span>
  </div>
</div>
```

```css
.banner {
  position: relative;
}
/* The photo fills the banner, and the link covers it. */
.banner img,
.banner-link {
  position: absolute;
  inset: 0;
}
.banner img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* The words sit over the photo and let clicks through to the link. */
.banner-text {
  position: relative;
  pointer-events: none;
}
.banner:hover .banner-cta {
  background: #fff;
  color: #2b1a10;
}
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
```

Adding the words leaves the layout as it is. The banner link is now announced as "Shop the roasts, link", and voice control finds it when someone says the words on the pill. On a banner built the second way, the same `span` goes in the empty link laid over it, and nothing else changes. When the photo is the link's only content, its `alt` can carry the words instead, `alt="Shop the roasts"`, as technique H30 describes: the alt then names where the link goes, not what the photo shows. The name uses the pill's words rather than the headline's because the pill is what reads as the thing to press: WCAG 2.5.3 Label in Name asks that a control's name contains the words that label it on screen, so people can say what they see. Instead of hidden text, the link can take its name from the pill with `aria-labelledby`, pointing at an `id` on the pill, or at the pill and then the headline (technique ARIA7), so the words can't drift apart. Either way, someone reading the banner from top to bottom hears the pill's words twice: once as the link and once as text.

## After: make the pill the link

```html
<div class="banner">
  <img src="/images/autumn-roasts.jpg" alt="">
  <div class="banner-text">
    <h2>Autumn roasts are here</h2>
    <p>Three new single-origin beans, roasted this week.</p>
    <a href="/collections/autumn-roasts" class="banner-cta">Shop the roasts<span aria-hidden="true"> →</span></a>
  </div>
</div>
```

```css
.banner {
  position: relative;
}
/* The photo fills the banner. */
.banner img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* The words sit over the photo, and their box fills the banner. */
.banner-text {
  position: relative;
}
/* The pill's click area covers the words' box, so the whole banner. */
.banner-cta::after {
  content: "";
  position: absolute;
  inset: 0;
}
.banner:hover .banner-cta {
  background: #fff;
  color: #2b1a10;
}
/* The ring goes round the whole banner, outside it, where the photo can't hide it. */
.banner-cta:focus-visible {
  outline: none;
}
.banner-cta:focus-visible::after {
  outline: 3px solid var(--focus-ring);
  outline-offset: 3px;
}
```

The pill becomes a real link, and the pseudo-element still makes the whole banner clickable: it covers the text's box, which fills the banner, and the text no longer passes clicks through. Its words are its name, so screen readers announce "Shop the roasts, link", and voice control users say what they see. The pill is the one to make the link, not the headline: it is what people read as the thing to press and what they would say, and its words name the destination, while the headline stays a heading, read once, for people who move by headings. If a banner has no pill, the headline is the link instead. The banner has one link, and nothing is read twice.

## Which option to use

Both options give the banner's link a name and meet 2.4.4. Adding the words is the smaller change: no layout changes, so it suits a theme or third-party slideshow you can't restructure. Making the pill the link is cleaner: one link, read once, named by the words people already see on the pill. Choose it when you can change the template. You can mix them, for example pill links on banners you own and a named overlay link on a slideshow you don't.

## Implementation decisions

**The stretched link covers other controls.** Any other button or link inside the banner, such as a second call to action or a slideshow's pause button, needs `position: relative` and a `z-index` above the pseudo-element, or it can't be clicked. The overlay also stops people selecting the banner's text.

**Wrapping the whole banner in one `<a>`.** This is valid HTML and makes the banner clickable, but the link's name becomes everything inside it, so the screen reader reads the headline, the text and the pill as one long link. The banner also can't contain another link or button. A link on the pill avoids both problems.

**Hidden text versus `aria-label`.** Use a clip-based `.visually-hidden` class, not `display: none`, which removes the text from the accessible name too. An `aria-label` on the empty link also gives it a name, but some browser translation tools skip it and it can drift from the pill, so prefer hidden text or `aria-labelledby`.

**Text over the photo.** The words sit on a photo, so they need a dark scrim under them, darkest behind the text, for their contrast; check it against the lightest part of the photo behind any word, at every width the banner takes.

**Image and icon links.** When the image is the only content of a link, its `alt` names the destination ("Northside Roasters home", "Instagram"), not the picture. When the image sits next to text in the same link, give it `alt=""` so the name is not read twice. For an SVG icon, hide the SVG with `aria-hidden="true"` and put visually hidden text in the link.

**Editable content.** In a theme or CMS, the banner's pill text is often an optional field. If it is left empty, the stretched link has no name again. Require a link label when a link is set, or don't render the link when the label is empty.

## Verify the fix

1. Tab through every link on the page and check, in the browser's accessibility tree, that each one has a name.
2. Open the screen reader's list of links (the rotor in VoiceOver, Insert + F7 in NVDA and JAWS). The banner's entry must say where it goes.
3. Check every image-only and icon-only link: the name describes the destination, not the picture or a file name.
4. Click across the banner, including at 200% zoom and on a touch screen, and check that other controls inside it can still be pressed.
5. With voice control, say "click" and the words on the banner's pill, and confirm that it follows the banner's link.

Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the shared banner or slideshow component changes.

## Limits

This is a starting pattern, not a guarantee for every application. Paths and class names in the snippets are placeholders. Content editors can still leave a banner's pill text empty later, so keep link names in your publishing review. Test the complete journey with your target assistive technology.

---

Get this guide in your coding agent: MCP https://easeweb.dev/mcp (setup at https://easeweb.dev/services/developers)
