[role="img"] and [role="image"] elements must have alternative text

Elements with role="img", such as background-image product photos, icon fonts or rating stars, need a text alternative. Without one, screen reader users do not know what the image shows, for example the star rating of a product or a colour swatch.

Rule id
role-img-alt
Source
axe-core 4.13.0
Impact
Serious
Fix tier
ASSISTED
on Shopify, Shopware, WooCommerce and Magento
Status
runs

What the check does

Ensure [role="img"] and [role="image"] elements have alternative text

The rule tests elements whose role attribute is img or image, as long as assistive technology can reach them. <img>, <area>, <input>, <object> and <svg> elements, and anything inside an <svg>, are outside this rule.

An element passes when one of three sources gives it text:

  • an aria-label that contains more than whitespace
  • an aria-labelledby that points to an element with text, even a hidden one
  • a title attribute with text

Nothing else counts. An alt attribute on a <div> fails, and text inside the element is not read. An empty title, or an aria-labelledby naming an id the page lacks, fails too.

By default Validiscan’s scanner tests each page at 1440 by 900 and 390 by 844 pixels, so an element hidden from assistive technology at one size is tested only at the other. A present label is all the rule asks for. Whether its words match the graphic, such as a star rating, or whether the element should be decorative instead, takes a person.

How to fix it

Add aria-label or aria-labelledby to every element with role="img" that describes what it conveys, such as aria-label="Rated 4.5 out of 5". If the element is purely decorative, remove role="img" and hide it with aria-hidden="true".

WCAG success criteria

EN 301 549 clauses

The EN 301 549 clause of each criterion, by version of the standard
WCAG criterion Version 3.2.1 Version 4.1.1
1.1.1 9.1.1.1 9.1.1.1

Recipes that list this rule

This rule has the tier ASSISTED. In a project, a finding of it takes the ASSISTED way, and these recipes are not applied to it.

Shopify: recipe decorative-image-alt

Show the before and after (1)
Before and after Example data

sections/footer.liquid Changes to sections/footer.liquid: 1 added, 1 removed

Changes to sections/footer.liquid: 1 added, 1 removed
Line before Line after Change Code
12 12 <input type="hidden" name="contact[tags]" value="newsletter">
13 13 <div class="newsletter-form__field-wrapper">
14 14 <input type="email" name="contact[email]" class="field__input" placeholder="{{ 'newsletter.label' | t }}" autocorrect="off" autocapitalize="off">
15 Removed <button type="submit" class="button newsletter-form__button" name="commit"><img src="{{ 'logo.svg' | asset_url }}" class="newsletter-form__icon" width="16" height="16">{{ 'newsletter.button' | t }}</button>
15 Added <button type="submit" class="button newsletter-form__button" name="commit"><img src="{{ 'logo.svg' | asset_url }}" class="newsletter-form__icon" width="16" height="16" alt="">{{ 'newsletter.button' | t }}</button>
16 16 </div>
17 17 {%- endform -%}
18 18 </div>

sections/header.liquid Changes to sections/header.liquid: 1 added, 1 removed

Changes to sections/header.liquid: 1 added, 1 removed
Line before Line after Change Code
46 46
47 47 <div class="header__icons">
48 48 {%- if section.settings.show_help_link %}
49 Removed <a href="{{ section.settings.help_link_url }}" class="header__utility-link link"><svg class="header__utility-icon" role="img" viewBox="0 0 24 24" width="16" height="16"><circle cx="12" cy="12" r="9"/></svg>{{ section.settings.help_link_label | escape }}</a>
49 Added <a href="{{ section.settings.help_link_url }}" class="header__utility-link link"><svg class="header__utility-icon" viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="9"/></svg>{{ section.settings.help_link_label | escape }}</a>
50 50 {%- endif %}
51 51 <a href="{{ routes.account_login_url }}" class="header__icon header__icon--account link">
52 52 {%- render 'icon', icon: 'account', size: icon_size -%}

Shopware: recipe decorative-image-alt

Show the before and after (1)
marks a seeded phone icon inside the hotline link decorative; image-alt stops firing there Example data

Resources/views/storefront/layout/footer/footer.html.twig Changes to Resources/views/storefront/layout/footer/footer.html.twig: 1 added, 1 removed

Changes to Resources/views/storefront/layout/footer/footer.html.twig: 1 added, 1 removed
Line before Line after Change Code
9 9 <div class="footer-column-content">
10 10 <p class="footer-contact-hotline">
11 11 {{ 'footer.serviceHotlineText'|trans }}
12 Removed <a class="footer-contact-hotline-link" href="tel:+49405550192"><img src="{{ asset('bundles/storefront/assets/icon/default/headset.svg') }}" class="footer-contact-hotline-icon" width="16" height="16"> {{ shop.company.phone }}</a>
12 Added <a class="footer-contact-hotline-link" href="tel:+49405550192"><img src="{{ asset('bundles/storefront/assets/icon/default/headset.svg') }}" class="footer-contact-hotline-icon" width="16" height="16" alt=""> {{ shop.company.phone }}</a>
13 13 </p>
14 14 <p class="footer-contact-hours">{{ 'footer.serviceHours'|trans }}</p>
15 15 <p class="footer-contact-form">

WooCommerce: recipe decorative-image-alt

Show the before and after (1)
hides the logo mark inside the home link that has the shop name Example data

header.php Changes to header.php: 1 added, 1 removed

Changes to header.php: 1 added, 1 removed
Line before Line after Change Code
39 39 <div class="site-branding">
40 40 <a href="<?php echo esc_url( home_url( '/' ) ); ?>" class="custom-logo-link" rel="home">
41 41 <?php // VALIDISCAN-SEED svg-img-alt: the logo mark is role="img" without a title or aria-label. ?>
42 Removed <svg class="kb-logo-mark" role="img" width="40" height="40" viewBox="0 0 40 40"><path d="M3 30c6-11 15-17 29-18-7 3-10 8-11 13 6-4 11-5 17-4-9 2-15 8-19 15L3 30Z" fill="#1d3b53"/></svg>
42 Added <svg class="kb-logo-mark" width="40" height="40" viewBox="0 0 40 40" aria-hidden="true" focusable="false"><path d="M3 30c6-11 15-17 29-18-7 3-10 8-11 13 6-4 11-5 17-4-9 2-15 8-19 15L3 30Z" fill="#1d3b53"/></svg>
43 43 <span class="kb-logo-text"><?php bloginfo( 'name' ); ?></span>
44 44 </a>
45 45 </div>

Magento: recipe decorative-image-alt

Show the before and after (1)
marks the promo tile image decorative and validates Example data

app/design/frontend/Validiscan/fixture/Magento_Cms/content/home.html Changes to app/design/frontend/Validiscan/fixture/Magento_Cms/content/home.html: 1 added, 1 removed

Changes to app/design/frontend/Validiscan/fixture/Magento_Cms/content/home.html: 1 added, 1 removed
Line before Line after Change Code
111 111 <div class="block-promo-wrapper block-promo-hp">
112 112 <!-- VALIDISCAN-SEEDED image-alt: promo image without an alt attribute -->
113 113 <a class="block-promo home-main" href="/magento/women/tops-women/jackets-women.html">
114 Removed <img src="/magento/media/wysiwyg/home/home-main.svg" width="400" height="240" />
114 Added <img src="/magento/media/wysiwyg/home/home-main.svg" width="400" height="240" alt="" />
115 115 <span class="content"
116 116 ><!-- VALIDISCAN-SEEDED color-contrast: .info text is #929292 on white (3.1:1) --><span
117 117 class="info"

Start with a free check of your shop.

Send your shop’s address. Validiscan scans it and replies with every failure the scan can prove, with the page and the code.

Request a free check

Or write to info@validiscan.com.

What the free check covers, and what it leaves out
You send
your shop’s address
Pages scanned
up to 15
You get
every failure the scan can prove, with the page and the code
On your shop
public pages only; no login, nothing bought, no forms sent
What it is not
a full audit

Write to Validiscan

For a free check, give the address of your shop and its platform.

Validiscan uses what you send to answer you. Privacy notice