# QR codes with no link to the page they open

Source: https://easeweb.dev/learn/qr-codes
Topics: Images and media > QR codes come with a link to what they hold (WCAG 1.1.1)
The fix: Either put a link to the same page beside the code, so everyone on a phone has something to tap and the code stays for people on a computer; or wrap the code in that link and give it alt text that says where it goes. The second passes 1.1.1, but on a phone people who can see the screen still find only a code to scan.
Test with: screen reader, touch, keyboard
References: https://www.w3.org/WAI/WCAG22/Understanding/non-text-content.html https://www.w3.org/WAI/tutorials/images/functional/ https://www.section508.gov/blog/accessibility-bytes/qr-codes/ https://tetralogical.com/blog/2022/08/08/accessibility-and-qr-codes/ https://kb.daisy.org/publishing/docs/html/images-qr.html

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 pottery shop confirms an order: "Thank you. Order 1042 is confirmed." Under it, a block offers the shop's app: track your orders and get 10% off your first order in the app. The way to the app is a QR code, with "Scan the code with your phone's camera" above it. On a computer, that works for someone who can see it: they hold up their phone and the app page opens there. Many people who place the order are on their phone already, and the same block is shown there, so the code is on the screen of the camera that is meant to scan it. Tapping it does nothing. A screen reader user, on either device, swipes to it and hears "QR code, image", and nothing about where it goes.

## Why it fails

A QR code is a picture of an address. Success criterion 1.1.1 Non-text Content asks that every image that carries information has a text alternative that gives the same information. Here the information is the address, or at least where it leads, and the alt says only "QR code": the listener learns that a code is there and nothing that it holds. The browser doesn't pass on what a code holds: to the page the code is an image like any other, and its alt is all a screen reader can say about it.

Nothing on the page links to the address either, so the code is the only way there. That is what turns a weak alt into a dead end: with a link beside the code, the address would have a text equivalent even with an empty alt on the image. No success criterion asks for a link a sighted person on a phone can tap, but the same link that fixes 1.1.1 fixes that too.

## Who is affected

On a phone the failure reaches nearly everyone, not only people with disabilities: the code is on the screen of the only camera most people have with them.

## What automation and AI miss

axe's `image-alt` checks that an image has an alt. It passes `alt="QR code"`, and it passes `alt="Scan me!"`, which qrcodejs, a widely copied script for drawing codes in the browser, puts on the image it draws. A code drawn on a `canvas` has no alt to check at all. axe doesn't decode the image to see what address it holds, or look for a link to that address.

The audit looks for codes by their alt, their file name, their class or their id. It reports a code whose alt says no more than "QR code" or "Scan me!", that is not inside a link and that has no link shown in its block. Every other code it finds goes to review, to check that the link beside it, or around it, opens the same page as the code. It can't tell a code from another square image when nothing names it, and it can't decode one.

A quick AI fix rewrites the alt as "QR code to download our app". The listener now knows what the code is for, and still has no way to get there.

## Before: a QR code as the only way to the app

```html
<section class="get-app">
  <h2>Our app</h2>
  <p>Track your orders and get 10% off your first order in the app.</p>
  <p>Scan the code with your phone's camera.</p>
  <img src="/images/qr-app.svg" alt="QR code" width="96" height="96">
</section>
```

- The alt says only "QR code", so the address the code holds has no text equivalent.
- Nothing on the page links to that address: the code is the only way there.
- The same block shows on a phone, where the code is on the screen of the camera that would scan it.

## After: a link beside the code

```html
<section class="get-app">
  <h2>Our app</h2>
  <p>Track your orders and get 10% off your first order in the app.</p>
  <a class="button" href="https://example.com/app">Get the app</a>
  <p>
    <img src="/images/qr-app.svg" alt="" width="56" height="56">
    On a computer? Scan the code with your phone's camera.
  </p>
</section>
```

1. Add a link to the address the code holds, with words that say what it opens. On a phone it is what people tap; on a computer it opens the page there, and keyboard, voice control and screen reader users can follow it.
2. Give the code `alt=""`. The link already says where it goes, so a screen reader doesn't read the same thing twice.
3. Keep the code, and its instruction, for people on a computer who want the page on their phone, and say that is what it is for.

This fixes the Before and passes 1.1.1: the address has a text equivalent in the link, and anyone on any device can follow it.

## After: the code itself as the link

```html
<section class="get-app">
  <h2>Our app</h2>
  <p>Track your orders and get 10% off your first order in the app.</p>
  <p>Scan the code with your phone's camera.</p>
  <a href="https://example.com/app">
    <img src="/images/qr-app.svg" alt="Get the app" width="96" height="96">
  </a>
</section>
```

1. Wrap the code in a link to the address it holds. A tap or a click on the code now opens the page.
2. Replace `alt="QR code"` with what the link does, "Get the app". As the link's only content, the alt is its name, so a screen reader reads the link as "Get the app".

This passes 1.1.1 with no change to the layout, and the code now opens the page for screen reader, keyboard and voice control users. It is not enough on its own: the page still tells people to scan, and nothing on screen says the code can be tapped, so a sighted person on a phone is left where they were unless they happen to try.

## Which option to use

Use the link beside the code. Wrapping the code in a link passes 1.1.1 and is the smaller change, but it leaves sighted phone users with a code they are told to scan and no sign they can tap it, which is the people this block is shown to most. The link beside the code serves everyone with one more link in the markup, and the code goes on working for people on a computer.

## Implementation decisions

The link and the code must open the same page. Make both from one value in the template, rather than pasting an address beside an image someone exported from a generator months ago. A code from a generator service often holds a short redirect of its own, for counting scans; give the link the same address, or one that ends on the same page, and count the two with different parameters if the numbers matter.

Some codes are made by a service, not by the site, and these come with a link from the same service. A messaging app's add-friend code comes with an add-friend address and button, and a payment provider's code with a payment link. Put that link, or that button, beside the code.

Sources differ on what a code's alt should say, and this guide doesn't choose between them:

- [The W3C's tutorial on images that are links](https://www.w3.org/WAI/tutorials/images/functional/) gives a linked image an alt that says what the link does, such as "Get the app", and [its tutorial on decorative images](https://www.w3.org/WAI/tutorials/images/decorative/) gives `alt=""` to an image whose text beside it already says the same.
- [The DAISY Knowledge Base](https://kb.daisy.org/publishing/docs/html/images-qr.html) asks for an alt that makes clear the image is a QR code, with a link to the same place beside it.
- [Section508.gov](https://www.section508.gov/blog/accessibility-bytes/qr-codes/) gives an example alt that says both: "QR code linking to the product accessibility page".

Each passes 1.1.1 as long as it says where the code goes; "QR code" alone does not.

Codes drawn in the browser need the alt set by hand: qrcodejs writes `alt="Scan me!"` on the image it makes, and a code drawn on a `canvas` has no text at all. When the link beside the code says where it goes, hide the drawn code with `alt=""` on the image, or `aria-hidden="true"` on the canvas. When the code is itself the link, give the link an `aria-label` that says where it goes.

Hiding the code on narrow screens is fine once the link is there. Hide the code, never the link, and decide by width rather than by guessing the device from its user agent: a tablet or a laptop with a touchscreen can go either way.

A code meant to be scanned by someone else's device is a different case. A ticket, a boarding pass or a pickup code on a phone is shown to a reader at a gate or a till, not opened by the person holding it. Its alt says what it is, such as "Ticket code for order 1042", and the number it holds is printed beside it as text, so staff can type it in when the scanner fails. Sign-in and two-factor set-up codes, which pair a second device, are the same failure as this one: offer a link that opens the authenticator app, and the set-up key as text that can be copied.

## Verify the fix

1. Find every QR code on the site: search templates and CMS blocks for "qr", and look at order confirmations, footers, app offers, sign-in and set-up pages. Check each has a link to the same page, beside it or around it.
2. With a screen reader on a phone, such as VoiceOver on iOS or TalkBack, swipe to each code or its link: you hear words that say where it goes, and a double tap opens that page.
3. Open the page at a phone width, or on a phone, and check that the link is shown and opens the page when tapped. Where the code itself is the link, ask whether someone who can see the page would know to tap it, and add a visible link if not.
4. On a computer, scan each code from the screen with a phone and check it opens the same page as the link.

Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat whenever a campaign, an app store address or a code generator changes.

## Limits

This is a starting pattern, not a guarantee for every application. The address in the snippets is a placeholder. This guide covers a QR code standing in for a link on a web page; for images that carry information in general, see [missing alt text on images that carry information](/learn/informative-images), and for words drawn into a picture beside the code, [images of text instead of real HTML text](/learn/images-of-text). Codes printed on paper, packaging or signs need a printed address beside them for the same reasons, but are outside what a web page can fix. 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)
