<svg> elements with an img or image role must have alternative text
Inline SVG images with an image role, such as logos, payment icons, badges or rating stars, need a text alternative. Without one, screen reader users miss the shop name in the logo, the accepted payment methods or the rating of a product.
- Rule id
- svg-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 <svg> elements with an img, image, graphics-document or graphics-symbol role have accessible text
The rule tests an <svg>, or an element inside one, that carries role="img", role="image" or role="graphics-symbol", and an <svg> with role="graphics-document". An <svg> without one of these roles is not tested, and a <div role="img"> belongs to the rule role-img-alt. Elements hidden from assistive technology, such as an icon with aria-hidden="true", are skipped.
Any one of four sources passes the element:
- a
<title>that is a direct child and contains text - an
aria-labelwith more than whitespace - an
aria-labelledbythat points to elements whose text is not empty - a
titleattribute with more than whitespace
A <title> nested deeper, for example inside a <circle> or <metadata>, does not name the outer <svg>. Neither a <desc> element nor <text> drawn inside the graphic counts, and an aria-labelledby pointing to an id that does not exist does not count either.
The rule only tests that a name exists. It does not judge whether the name describes the logo, payment method or rating it stands for.
How to fix it
Give every <svg role="img"> an accessible name with a <title> element as its first child, aria-label or aria-labelledby, such as aria-label="Visa". Decorative SVGs should have aria-hidden="true" and no img role.
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