[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-labelthat contains more than whitespace - an
aria-labelledbythat points to an element with text, even a hidden one - a
titleattribute 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
- 1.1.1 Non-text Content, level A
EN 301 549 clauses
| 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)
sections/footer.liquid 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
| 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)
Resources/views/storefront/layout/footer/footer.html.twig 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)
header.php 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)
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
| 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 checkOr 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