Cookie banner must be keyboard operable and manage focus correctly
The cookie banner is the first thing every visitor meets. When keyboard or screen reader users cannot reach its buttons, cannot close it, or lose their place once it closes, they are stuck before they have seen a single product, and most of them leave the shop.
- Rule id
- vs-cookie-banner
- Source
- Validiscan check
- Impact
- Critical
- Fix tier
- ASSISTED
- on Shopify, Shopware, WooCommerce and Magento
- Status
- runs
What the check does
Checks that the consent banner or dialog can be reached and operated with the keyboard, keeps focus inside while it blocks the page, offers a reachable close control or Escape, has an accessible name and sufficient contrast, and returns focus to the page when it closes.
A banner is a box with text and controls: a consent platform container, or a dialog, fixed, sticky or high-z-index absolute element whose id, class or text mentions cookies, consent or privacy. As the scanner accepts banners by default, a page showing none is reopened without cookies, once per scan and viewport.
Focus not entering the banner within 30 Tab presses from the top of the page is a finding. A banner with aria-modal, a modal <dialog>, locked page scroll or a viewport-covering overlay is modal and must already contain focus or receive it on the first Tab.
In a throwaway copy of the page, Escape must close a modal banner, other than a native <dialog>, that has no focusable reject or close control, and the reject, close or accept control gets Enter, Space, then a click. Closing only on the click is a finding, as is focus lost to the page body after a keyboard close. Undecided outcomes go to needs review, which yields no finding today.
How to fix it
Use real <button> elements for accept, reject and settings; give the banner role="dialog" with a name (aria-labelledby pointing at its heading); move focus into it when it opens, keep Tab inside while it blocks the page, close it with Escape and return focus to where the visitor was. Most banners come from a consent platform: enable its accessible layout or accessibility options before patching theme code.
WCAG success criteria
- 2.1.1 Keyboard, level A
- 2.1.2 No Keyboard Trap, level A
- 2.4.3 Focus Order, level A
- 4.1.2 Name, Role, Value, level A
EN 301 549 clauses
| WCAG criterion | Version 3.2.1 | Version 4.1.1 |
|---|---|---|
| 2.1.1 | 9.2.1.1 | 9.2.1.1 |
| 2.1.2 | 9.2.1.2 | 9.2.1.2 |
| 2.4.3 | 9.2.4.3 | 9.2.4.3 |
| 4.1.2 | 9.4.1.2 | 9.4.1.2 |
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 button-semantics
Show the before and after (1)
assets/base.css.liquid Changes to assets/base.css.liquid: 14 added, 0 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 1866 | 1866 | } | |
| 1867 | 1867 | ||
| 1868 | 1868 | /* ----------------------------------------------------------------- motion */ | |
| 1869 | Added | ||
| 1870 | Added | /* validiscan:button-semantics */ | |
| 1871 | Added | :where(button.slider-counter__link) { | |
| 1872 | Added | appearance: none; | |
| 1873 | Added | margin: 0; | |
| 1874 | Added | padding: 0; | |
| 1875 | Added | border: 0; | |
| 1876 | Added | background: none; | |
| 1877 | Added | color: inherit; | |
| 1878 | Added | font: inherit; | |
| 1879 | Added | text-align: inherit; | |
| 1880 | Added | cursor: pointer; | |
| 1881 | Added | } | |
| 1882 | Added | /* /validiscan:button-semantics */ |
sections/slideshow.liquid Changes to sections/slideshow.liquid: 1 added, 1 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 62 | 62 | </button> | |
| 63 | 63 | <div class="slider-counter"> | |
| 64 | 64 | {%- for block in section.blocks %} | |
| 65 | Removed | <span class="slider-counter__link" data-slide-to="{{ forloop.index0 }}"><span class="dot"></span></span> | |
| 65 | Added | <button class="slider-counter__link" data-slide-to="{{ forloop.index0 }}" type="button"><span class="dot"></span><span class="visually-hidden">{{ 'accessibility.load_slide' | t: index: forloop.index, count: forloop.length }}</span></button> | |
| 66 | 66 | {%- endfor %} | |
| 67 | 67 | </div> | |
| 68 | 68 | <button type="button" class="slider-button slider-button--next" name="next" data-slide-next> |
Shopify: recipe icon-accessible-name
Show the before and after (2)
locales/en.default.json Changes to locales/en.default.json: 3 added, 1 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 34 | 34 | "total_reviews": "{{ count }} reviews", | |
| 35 | 35 | "link_messages": { | |
| 36 | 36 | "new_window": "Opens in a new window." | |
| 37 | Removed | } | |
| 37 | Added | }, | |
| 38 | Added | "accept_cookies": "Accept cookies", | |
| 39 | Added | "reject_cookies": "Reject cookies" | |
| 38 | 40 | }, | |
| 39 | 41 | "sections": { | |
| 40 | 42 | "header": { |
sections/header.liquid Changes to sections/header.liquid: 1 added, 0 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 40 | 40 | <input class="search__input field__input" type="search" name="q" value="" placeholder="{{ 'general.search.placeholder' | t }}" tabindex="1"> | |
| 41 | 41 | <button type="submit" class="search__button"> | |
| 42 | 42 | {%- render 'icon', icon: 'search', size: 20 -%} | |
| 43 | Added | <span class="visually-hidden">{{ 'general.search.search' | t }}</span> | |
| 43 | 44 | </button> | |
| 44 | 45 | </form> | |
| 45 | 46 | </div> |
sections/main-product.liquid Changes to sections/main-product.liquid: 1 added, 1 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 88 | 88 | <p class="form__label">{{ option.name | escape }}</p> | |
| 89 | 89 | <div class="swatch-list swatch-list--compact"> | |
| 90 | 90 | {%- for swatch in option.swatches %} | |
| 91 | Removed | <button type="button" class="swatch swatch--button{% if swatch.selected %} swatch--active{% endif %}" style="--swatch-color: {{ swatch.color }};" data-option-index="{{ option_index }}" data-value="{{ swatch.value | escape }}"></button> | |
| 91 | Added | <button type="button" class="swatch swatch--button{% if swatch.selected %} swatch--active{% endif %}" style="--swatch-color: {{ swatch.color }};" data-option-index="{{ option_index }}" data-value="{{ swatch.value | escape }}"><span class="visually-hidden">{{ swatch.value | escape }}</span></button> | |
| 92 | 92 | {%- endfor %} | |
| 93 | 93 | </div> | |
| 94 | 94 | </div> |
sections/slideshow.liquid Changes to sections/slideshow.liquid: 2 added, 0 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 59 | 59 | <div class="slideshow__controls"> | |
| 60 | 60 | <button type="button" class="slider-button slider-button--prev" name="previous" data-slide-prev> | |
| 61 | 61 | {%- render 'icon', icon: 'chevron-left', size: 20 -%} | |
| 62 | Added | <span class="visually-hidden">{{ 'accessibility.previous_slide' | t }}</span> | |
| 62 | 63 | </button> | |
| 63 | 64 | <div class="slider-counter"> | |
| 64 | 65 | {%- for block in section.blocks %} | |
| 67 | 68 | </div> | |
| 68 | 69 | <button type="button" class="slider-button slider-button--next" name="next" data-slide-next> | |
| 69 | 70 | {%- render 'icon', icon: 'chevron-right', size: 20 -%} | |
| 71 | Added | <span class="visually-hidden">{{ 'accessibility.next_slide' | t }}</span> | |
| 70 | 72 | </button> | |
| 71 | 73 | </div> | |
| 72 | 74 | </slideshow-component> |
snippets/cookie-banner.liquid Changes to snippets/cookie-banner.liquid: 2 added, 0 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 23 | 23 | <div class="cookie-banner__actions"> | |
| 24 | 24 | <button type="button" class="cookie-banner__icon-button cookie-banner__icon-button--accept" data-cookie-accept> | |
| 25 | 25 | {%- render 'icon', icon: 'check', size: 20 -%} | |
| 26 | Added | <span class="visually-hidden">{{ 'accessibility.accept_cookies' | t }}</span> | |
| 26 | 27 | </button> | |
| 27 | 28 | <button type="button" class="cookie-banner__icon-button" data-cookie-reject> | |
| 28 | 29 | {%- render 'icon', icon: 'close', size: 20 -%} | |
| 30 | Added | <span class="visually-hidden">{{ 'accessibility.reject_cookies' | t }}</span> | |
| 29 | 31 | </button> | |
| 30 | 32 | </div> | |
| 31 | 33 | </div> |
snippets/quantity-input.liquid Changes to snippets/quantity-input.liquid: 2 added, 0 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 9 | 9 | {%- endcomment -%} | |
| 10 | 10 | <div class="quantity quantity--compact" data-quantity{% if line %} data-line="{{ line }}"{% endif %}><button class="quantity__button" name="minus" type="button" data-quantity-minus> | |
| 11 | 11 | {%- render 'icon', icon: 'minus', size: 12 -%} | |
| 12 | Added | <span class="visually-hidden">{{ 'products.product.quantity.decrease' | t: product: product_title }}</span> | |
| 12 | 13 | </button> | |
| 13 | 14 | <input class="quantity__input" type="number" name="{{ input_name }}" id="{{ input_id }}" value="{{ value | default: 1 }}" min="{% if line %}0{% else %}1{% endif %}" data-quantity-input> | |
| 14 | 15 | <button class="quantity__button" name="plus" type="button" data-quantity-plus> | |
| 15 | 16 | {%- render 'icon', icon: 'plus', size: 12 -%} | |
| 17 | Added | <span class="visually-hidden">{{ 'products.product.quantity.increase' | t: product: product_title }}</span> | |
| 16 | 18 | </button></div> |
sections/footer.liquid Changes to sections/footer.liquid: 1 added, 0 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 37 | 37 | {%- for social in shop.social %} | |
| 38 | 38 | <a href="{{ social.url }}" class="list-social__link link"> | |
| 39 | 39 | {%- render 'icon', icon: social.icon, size: 20 -%} | |
| 40 | Added | <span class="visually-hidden">{{ 'general.social.links.' | append: social.icon | t }}</span> | |
| 40 | 41 | </a> | |
| 41 | 42 | {%- endfor %} | |
| 42 | 43 | </ul> |
sections/header.liquid Changes to sections/header.liquid: 3 added, 1 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 22 | 22 | </details> | |
| 23 | 23 | ||
| 24 | 24 | {% if logo_is_heading %}<h1 class="header__heading">{% else %}<div class="header__heading">{% endif %} | |
| 25 | Removed | <a href="{{ routes.root_url }}" class="header__heading-link"><img src="{{ 'logo.svg' | asset_url }}" width="{{ settings.logo_width }}" height="32" class="header__heading-logo"></a> | |
| 25 | Added | <a href="{{ routes.root_url }}" class="header__heading-link"><img src="{{ 'logo.svg' | asset_url }}" width="{{ settings.logo_width }}" height="32" class="header__heading-logo" alt="{{ shop.name | escape }}"></a> | |
| 26 | 26 | {% if logo_is_heading %}</h1>{% else %}</div>{% endif %} | |
| 27 | 27 | ||
| 28 | 28 | <nav class="header__inline-menu"> | |
| 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 -%} | |
| 53 | Added | <span class="visually-hidden">{{ 'sections.header.account' | t }}</span> | |
| 53 | 54 | </a> | |
| 54 | 55 | <a href="{{ routes.cart_url }}" class="header__icon header__icon--cart link" id="cart-icon-bubble"> | |
| 55 | 56 | {%- render 'icon', icon: 'cart', size: icon_size -%} | |
| 57 | 58 | <span class="cart-count-bubble" aria-hidden="true"><span data-cart-count>{{ cart.item_count }}</span></span> | |
| 58 | 59 | {%- endif -%} | |
| 59 | 60 | ||
| 61 | Added | <span class="visually-hidden">{{ 'sections.header.cart' | t }}</span> | |
| 60 | 62 | </a> | |
| 61 | 63 | </div> | |
| 62 | 64 | </header> |
sections/main-cart.liquid Changes to sections/main-cart.liquid: 1 added, 0 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 26 | 26 | {% render 'quantity-input', input_id: qty_id, input_name: 'updates[]', value: item.quantity, product_title: item.product.title, line: line %} | |
| 27 | 27 | <a href="{{ routes.cart_change_url }}?line={{ line }}&quantity=0" class="cart-item__remove cart-item__remove--compact" data-cart-remove> | |
| 28 | 28 | {%- render 'icon', icon: 'trash', size: 16 -%} | |
| 29 | Added | <span class="visually-hidden">{{ 'sections.cart.remove' | t: title: item.product.title }}</span> | |
| 29 | 30 | </a> | |
| 30 | 31 | </div> | |
| 31 | 32 | <div class="cart-item__totals"> |
Shopware: recipe button-semantics
Show the before and after (1)
Resources/app/storefront/src/scss/base.scss Changes to Resources/app/storefront/src/scss/base.scss: 17 added, 0 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 1708 | 1708 | margin: 8px 0 0; | |
| 1709 | 1709 | font-size: 0.875rem; | |
| 1710 | 1710 | } | |
| 1711 | Added | ||
| 1712 | Added | /* validiscan:button-semantics */ | |
| 1713 | Added | /* Former <div>/<span>/<li> controls, now <button>: drop the browser button styles, keep the theme's (no specificity). */ | |
| 1714 | Added | :where(button.base-slider-dot) { | |
| 1715 | Added | display: block; | |
| 1716 | Added | margin: 0; | |
| 1717 | Added | padding: 0; | |
| 1718 | Added | border: 0; | |
| 1719 | Added | background: none; | |
| 1720 | Added | color: inherit; | |
| 1721 | Added | font: inherit; | |
| 1722 | Added | line-height: inherit; | |
| 1723 | Added | text-align: inherit; | |
| 1724 | Added | cursor: pointer; | |
| 1725 | Added | appearance: none; | |
| 1726 | Added | } | |
| 1727 | Added | /* /validiscan:button-semantics */ |
Resources/snippet/de_DE/kaffeekontor-nord.de-DE.json Changes to Resources/snippet/de_DE/kaffeekontor-nord.de-DE.json: 5 added, 0 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 193 | 193 | "notFoundText": "Die gesuchte Seite existiert nicht oder wurde verschoben.", | |
| 194 | 194 | "backToHome": "Zurück zur Startseite", | |
| 195 | 195 | "searchInstead": "Unsere Kaffees durchsuchen" | |
| 196 | Added | }, | |
| 197 | Added | "a11y": { | |
| 198 | Added | "control": { | |
| 199 | Added | "showSlide": "Folie %index% anzeigen" | |
| 200 | Added | } | |
| 196 | 201 | } | |
| 197 | 202 | } |
Resources/snippet/en_GB/kaffeekontor-nord.en-GB.json Changes to Resources/snippet/en_GB/kaffeekontor-nord.en-GB.json: 5 added, 0 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 193 | 193 | "notFoundText": "The page you are looking for does not exist or has moved.", | |
| 194 | 194 | "backToHome": "Back to the homepage", | |
| 195 | 195 | "searchInstead": "Search our coffees" | |
| 196 | Added | }, | |
| 197 | Added | "a11y": { | |
| 198 | Added | "control": { | |
| 199 | Added | "showSlide": "Show slide %index%" | |
| 200 | Added | } | |
| 196 | 201 | } | |
| 197 | 202 | } |
Resources/views/storefront/element/cms-element-image-slider.html.twig Changes to Resources/views/storefront/element/cms-element-image-slider.html.twig: 1 added, 1 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 51 | 51 | </button> | |
| 52 | 52 | <ol class="base-slider-dots"> | |
| 53 | 53 | {% for slide in slides %} | |
| 54 | Removed | <li class="base-slider-dot{% if loop.first %} is-active{% endif %}" data-slide-to="{{ loop.index0 }}"></li> | |
| 54 | Added | <li><button class="base-slider-dot{% if loop.first %} is-active{% endif %}" data-slide-to="{{ loop.index0 }}" type="button" aria-label="{{ 'a11y.control.showSlide'|trans({'%index%': loop.index})|striptags }}"></button></li> | |
| 55 | 55 | {% endfor %} | |
| 56 | 56 | </ol> | |
| 57 | 57 | <button type="button" class="base-slider-controls-next image-slider-controls-btn" data-slide-next> |
Shopware: recipe icon-accessible-name
Show the before and after (2)
Resources/snippet/de_DE/kaffeekontor-nord.de-DE.json Changes to Resources/snippet/de_DE/kaffeekontor-nord.de-DE.json: 12 added, 0 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 193 | 193 | "notFoundText": "Die gesuchte Seite existiert nicht oder wurde verschoben.", | |
| 194 | 194 | "backToHome": "Zurück zur Startseite", | |
| 195 | 195 | "searchInstead": "Unsere Kaffees durchsuchen" | |
| 196 | Added | }, | |
| 197 | Added | "a11y": { | |
| 198 | Added | "control": { | |
| 199 | Added | "search": "Suchen", | |
| 200 | Added | "previousSlide": "Vorherige Folie", | |
| 201 | Added | "nextSlide": "Nächste Folie", | |
| 202 | Added | "cookieSettings": "Cookie-Einstellungen", | |
| 203 | Added | "acceptCookies": "Alle Cookies akzeptieren", | |
| 204 | Added | "decreaseQuantity": "Menge verringern", | |
| 205 | Added | "increaseQuantity": "Menge erhöhen", | |
| 206 | Added | "removeFromCart": "Aus dem Warenkorb entfernen" | |
| 207 | Added | } | |
| 196 | 208 | } | |
| 197 | 209 | } |
Resources/snippet/en_GB/kaffeekontor-nord.en-GB.json Changes to Resources/snippet/en_GB/kaffeekontor-nord.en-GB.json: 12 added, 0 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 193 | 193 | "notFoundText": "The page you are looking for does not exist or has moved.", | |
| 194 | 194 | "backToHome": "Back to the homepage", | |
| 195 | 195 | "searchInstead": "Search our coffees" | |
| 196 | Added | }, | |
| 197 | Added | "a11y": { | |
| 198 | Added | "control": { | |
| 199 | Added | "search": "Search", | |
| 200 | Added | "previousSlide": "Previous slide", | |
| 201 | Added | "nextSlide": "Next slide", | |
| 202 | Added | "cookieSettings": "Cookie settings", | |
| 203 | Added | "acceptCookies": "Accept all cookies", | |
| 204 | Added | "decreaseQuantity": "Decrease quantity", | |
| 205 | Added | "increaseQuantity": "Increase quantity", | |
| 206 | Added | "removeFromCart": "Remove from cart" | |
| 207 | Added | } | |
| 196 | 208 | } | |
| 197 | 209 | } |
Resources/views/storefront/element/cms-element-image-gallery.html.twig Changes to Resources/views/storefront/element/cms-element-image-gallery.html.twig: 2 added, 2 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 31 | 31 | ||
| 32 | 32 | {% if media|length > 1 %} | |
| 33 | 33 | <div class="gallery-slider-controls"> | |
| 34 | Removed | <button type="button" class="base-slider-controls-prev gallery-slider-controls-prev" data-gallery-prev> | |
| 34 | Added | <button type="button" class="base-slider-controls-prev gallery-slider-controls-prev" data-gallery-prev aria-label="{{ 'a11y.control.previousSlide'|trans|striptags }}"> | |
| 35 | 35 | {% sw_icon 'arrow-head-left' %} | |
| 36 | 36 | </button> | |
| 37 | Removed | <button type="button" class="base-slider-controls-next gallery-slider-controls-next" data-gallery-next> | |
| 37 | Added | <button type="button" class="base-slider-controls-next gallery-slider-controls-next" data-gallery-next aria-label="{{ 'a11y.control.nextSlide'|trans|striptags }}"> | |
| 38 | 38 | {% sw_icon 'arrow-head-right' %} | |
| 39 | 39 | </button> | |
| 40 | 40 | </div> |
Resources/views/storefront/element/cms-element-image-slider.html.twig Changes to Resources/views/storefront/element/cms-element-image-slider.html.twig: 2 added, 2 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 46 | 46 | ||
| 47 | 47 | {% block element_image_slider_controls %} | |
| 48 | 48 | <div class="image-slider-controls-container"> | |
| 49 | Removed | <button type="button" class="base-slider-controls-prev image-slider-controls-btn" data-slide-prev> | |
| 49 | Added | <button type="button" class="base-slider-controls-prev image-slider-controls-btn" data-slide-prev aria-label="{{ 'a11y.control.previousSlide'|trans|striptags }}"> | |
| 50 | 50 | {% sw_icon 'arrow-head-left' %} | |
| 51 | 51 | </button> | |
| 52 | 52 | <ol class="base-slider-dots"> | |
| 54 | 54 | <li class="base-slider-dot{% if loop.first %} is-active{% endif %}" data-slide-to="{{ loop.index0 }}"></li> | |
| 55 | 55 | {% endfor %} | |
| 56 | 56 | </ol> | |
| 57 | Removed | <button type="button" class="base-slider-controls-next image-slider-controls-btn" data-slide-next> | |
| 57 | Added | <button type="button" class="base-slider-controls-next image-slider-controls-btn" data-slide-next aria-label="{{ 'a11y.control.nextSlide'|trans|striptags }}"> | |
| 58 | 58 | {% sw_icon 'arrow-head-right' %} | |
| 59 | 59 | </button> | |
| 60 | 60 | </div> |
Resources/views/storefront/layout/cookie/cookie-permission.html.twig Changes to Resources/views/storefront/layout/cookie/cookie-permission.html.twig: 2 added, 2 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 22 | 22 | </p> | |
| 23 | 23 | </div> | |
| 24 | 24 | <div class="col-auto cookie-permission-actions"> | |
| 25 | Removed | <button type="button" class="btn cookie-permission-icon-btn js-cookie-configuration-toggle">{% sw_icon 'settings' %}</button> | |
| 26 | Removed | <button type="button" class="btn btn-primary cookie-permission-icon-btn js-cookie-accept-all-button">{% sw_icon 'checkmark' %}</button> | |
| 25 | Added | <button type="button" class="btn cookie-permission-icon-btn js-cookie-configuration-toggle" aria-label="{{ 'a11y.control.cookieSettings'|trans|striptags }}">{% sw_icon 'settings' %}</button> | |
| 26 | Added | <button type="button" class="btn btn-primary cookie-permission-icon-btn js-cookie-accept-all-button" aria-label="{{ 'a11y.control.acceptCookies'|trans|striptags }}">{% sw_icon 'checkmark' %}</button> | |
| 27 | 27 | </div> | |
| 28 | 28 | </div> | |
| 29 | 29 | <p class="cookie-permission-configuration" hidden>{{ 'cookie.configurationText'|trans }}</p> |
Resources/views/storefront/layout/header/search.html.twig Changes to Resources/views/storefront/layout/header/search.html.twig: 1 added, 1 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 13 | 13 | autocomplete="off" | |
| 14 | 14 | autocapitalize="off" | |
| 15 | 15 | placeholder="{{ 'header.searchPlaceholder'|trans }}" value="{{ page.searchTerm }}"> | |
| 16 | Removed | <button type="submit" class="btn header-search-btn"> | |
| 16 | Added | <button type="submit" class="btn header-search-btn" aria-label="{{ 'a11y.control.search'|trans|striptags }}"> | |
| 17 | 17 | <span class="header-search-icon">{% sw_icon 'search' %}</span> | |
| 18 | 18 | </button> | |
| 19 | 19 | </div> |
Resources/views/storefront/page/checkout/cart/index.html.twig Changes to Resources/views/storefront/page/checkout/cart/index.html.twig: 3 added, 3 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 39 | 39 | </div> | |
| 40 | 40 | <div class="line-item-quantity"> | |
| 41 | 41 | <div class="input-group quantity-selector-group" data-quantity-selector="true"> | |
| 42 | Removed | <button type="button" class="btn btn-outline-light btn-minus js-btn-minus"> | |
| 42 | Added | <button type="button" class="btn btn-outline-light btn-minus js-btn-minus" aria-label="{{ 'a11y.control.decreaseQuantity'|trans|striptags }}"> | |
| 43 | 43 | {% sw_icon 'minus' style { size: 'xs' } %} | |
| 44 | 44 | </button> | |
| 45 | 45 | <input type="number" | |
| 50 | 50 | max="100" | |
| 51 | 51 | step="1" | |
| 52 | 52 | value="{{ item.quantity }}"> | |
| 53 | Removed | <button type="button" class="btn btn-outline-light btn-plus js-btn-plus"> | |
| 53 | Added | <button type="button" class="btn btn-outline-light btn-plus js-btn-plus" aria-label="{{ 'a11y.control.increaseQuantity'|trans|striptags }}"> | |
| 54 | 54 | {% sw_icon 'plus' style { size: 'xs' } %} | |
| 55 | 55 | </button> | |
| 56 | 56 | </div> | |
| 64 | 64 | <span class="line-item-total-price-value" data-line-total>{{ item.totalPrice|currency }}*</span> | |
| 65 | 65 | </div> | |
| 66 | 66 | <div class="line-item-remove"> | |
| 67 | Removed | <button type="button" class="btn btn-light btn-sm line-item-remove-button"> | |
| 67 | Added | <button type="button" class="btn btn-light btn-sm line-item-remove-button" aria-label="{{ 'a11y.control.removeFromCart'|trans|striptags }}"> | |
| 68 | 68 | {% sw_icon 'x' style { size: 'xs' } %} | |
| 69 | 69 | </button> | |
| 70 | 70 | </div> |
Resources/views/storefront/page/product-detail/buy-widget.html.twig Changes to Resources/views/storefront/page/product-detail/buy-widget.html.twig: 2 added, 2 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 64 | 64 | <div class="buy-widget-container"> | |
| 65 | 65 | <div class="buy-widget-quantity"> | |
| 66 | 66 | <div class="input-group quantity-selector-group" data-quantity-selector="true"> | |
| 67 | Removed | <button type="button" class="btn btn-outline-light btn-minus js-btn-minus"> | |
| 67 | Added | <button type="button" class="btn btn-outline-light btn-minus js-btn-minus" aria-label="{{ 'a11y.control.decreaseQuantity'|trans|striptags }}"> | |
| 68 | 68 | {% sw_icon 'minus' style { size: 'xs' } %} | |
| 69 | 69 | </button> | |
| 70 | 70 | <input type="number" | |
| 75 | 75 | max="100" | |
| 76 | 76 | step="1" | |
| 77 | 77 | value="1"> | |
| 78 | Removed | <button type="button" class="btn btn-outline-light btn-plus js-btn-plus"> | |
| 78 | Added | <button type="button" class="btn btn-outline-light btn-plus js-btn-plus" aria-label="{{ 'a11y.control.increaseQuantity'|trans|striptags }}"> | |
| 79 | 79 | {% sw_icon 'plus' style { size: 'xs' } %} | |
| 80 | 80 | </button> | |
| 81 | 81 | </div> |
Resources/snippet/de_DE/kaffeekontor-nord.de-DE.json Changes to Resources/snippet/de_DE/kaffeekontor-nord.de-DE.json: 6 added, 0 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 193 | 193 | "notFoundText": "Die gesuchte Seite existiert nicht oder wurde verschoben.", | |
| 194 | 194 | "backToHome": "Zurück zur Startseite", | |
| 195 | 195 | "searchInstead": "Unsere Kaffees durchsuchen" | |
| 196 | Added | }, | |
| 197 | Added | "a11y": { | |
| 198 | Added | "control": { | |
| 199 | Added | "home": "Zur Startseite", | |
| 200 | Added | "account": "Ihr Konto" | |
| 201 | Added | } | |
| 196 | 202 | } | |
| 197 | 203 | } |
Resources/snippet/en_GB/kaffeekontor-nord.en-GB.json Changes to Resources/snippet/en_GB/kaffeekontor-nord.en-GB.json: 6 added, 0 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 193 | 193 | "notFoundText": "The page you are looking for does not exist or has moved.", | |
| 194 | 194 | "backToHome": "Back to the homepage", | |
| 195 | 195 | "searchInstead": "Search our coffees" | |
| 196 | Added | }, | |
| 197 | Added | "a11y": { | |
| 198 | Added | "control": { | |
| 199 | Added | "home": "Go to the homepage", | |
| 200 | Added | "account": "Your account" | |
| 201 | Added | } | |
| 196 | 202 | } | |
| 197 | 203 | } |
Resources/views/storefront/layout/header/actions.html.twig Changes to Resources/views/storefront/layout/header/actions.html.twig: 2 added, 1 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 3 | 3 | {% block layout_header_actions_account %} | |
| 4 | 4 | <div class="header-account"> | |
| 5 | 5 | <a href="{{ url('/account/login') }}" | |
| 6 | Removed | class="btn account-menu-btn header-actions-btn"> | |
| 6 | Added | class="btn account-menu-btn header-actions-btn" | |
| 7 | Added | aria-label="{{ 'a11y.control.account'|trans|striptags }}"> | |
| 7 | 8 | {% sw_icon 'avatar' %} | |
| 8 | 9 | </a> | |
| 9 | 10 | </div> |
Resources/views/storefront/layout/header/logo.html.twig Changes to Resources/views/storefront/layout/header/logo.html.twig: 1 added, 1 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 2 | 2 | <div class="header-logo-main"> | |
| 3 | 3 | {% block layout_header_logo_link %} | |
| 4 | 4 | {# The agency removed the default title="Go to homepage" and the image alt to "avoid duplicate announcements". #} | |
| 5 | Removed | <a class="header-logo-main-link" href="{{ url('/') }}"> | |
| 5 | Added | <a class="header-logo-main-link" href="{{ url('/') }}" aria-label="{{ 'a11y.control.home'|trans|striptags }}"> | |
| 6 | 6 | {% block layout_header_logo_image %} | |
| 7 | 7 | <picture class="header-logo-picture"> | |
| 8 | 8 | <img src="{{ asset('bundles/storefront/assets/logo/kaffeekontor-nord-logo.svg') }}" |
WooCommerce: recipe button-semantics
Show the before and after (1)
assets/css/main.css Changes to assets/css/main.css: 17 added, 0 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 766 | 766 | gap: 0.75rem; | |
| 767 | 767 | } | |
| 768 | 768 | ||
| 769 | Added | /* validiscan:button-semantics */ | |
| 770 | Added | /* Controls that became <button>s: keep the look of the elements they replace. The theme | |
| 771 | Added | rules for these classes follow and still win. */ | |
| 769 | 772 | .kb-cookie-bar__settings, | |
| 770 | 773 | .kb-cookie-bar__accept { | |
| 774 | Added | margin: 0; | |
| 775 | Added | padding: 0; | |
| 776 | Added | border: 0; | |
| 777 | Added | background: none; | |
| 778 | Added | color: inherit; | |
| 779 | Added | font: inherit; | |
| 780 | Added | line-height: inherit; | |
| 781 | Added | text-align: inherit; | |
| 782 | Added | cursor: pointer; | |
| 783 | Added | } | |
| 784 | Added | /* /validiscan:button-semantics */ | |
| 785 | Added | ||
| 786 | Added | .kb-cookie-bar__settings, | |
| 787 | Added | .kb-cookie-bar__accept { | |
| 771 | 788 | padding: 0.6em 1.1em; | |
| 772 | 789 | border-radius: 4px; | |
| 773 | 790 | font-size: 0.9rem; |
template-parts/cookie-bar.php Changes to template-parts/cookie-bar.php: 3 added, 3 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 21 | 21 | <a href="<?php echo esc_url( get_privacy_policy_url() ); ?>"><?php esc_html_e( 'Privacy policy', 'kestrel-birch' ); ?></a> | |
| 22 | 22 | </p> | |
| 23 | 23 | <div class="kb-cookie-bar__actions"> | |
| 24 | Removed | <span class="kb-cookie-bar__settings" data-kb-consent="settings"><?php esc_html_e( 'Cookie settings', 'kestrel-birch' ); ?></span> | |
| 25 | Removed | <span class="kb-cookie-bar__accept" data-kb-consent="accept"><?php esc_html_e( 'Accept', 'kestrel-birch' ); ?></span> | |
| 26 | Removed | <button type="button" class="kb-cookie-bar__close" data-kb-consent="close"><?php kb_icon( 'close' ); ?></button> | |
| 24 | Added | <button class="kb-cookie-bar__settings" data-kb-consent="settings" type="button"><?php esc_html_e( 'Cookie settings', 'kestrel-birch' ); ?></button> | |
| 25 | Added | <button class="kb-cookie-bar__accept" data-kb-consent="accept" type="button"><?php esc_html_e( 'Accept', 'kestrel-birch' ); ?></button> | |
| 26 | Added | <button type="button" class="kb-cookie-bar__close" data-kb-consent="close"><?php kb_icon( 'close' ); ?><span class="screen-reader-text"><?php esc_html_e( 'Close', 'kestrel-birch' ); ?></span></button> | |
| 27 | 27 | </div> | |
| 28 | 28 | </div> |
Magento: recipe button-semantics
Show the before and after (1)
app/design/frontend/Validiscan/fixture/Magento_Cookie/templates/html/notices.phtml Changes to app/design/frontend/Validiscan/fixture/Magento_Cookie/templates/html/notices.phtml: 2 added, 2 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 24 | 24 | <div class="amgdprcookie-bar-block"> | |
| 25 | 25 | <p class="amgdprcookie-policy"><?= $escaper->escapeHtml(__('We use cookies to give you the best shopping experience and to analyse our traffic. By continuing to browse you agree to our')) ?> <a href="<?= $escaper->escapeUrl($block->getPrivacyPolicyLink()) ?>" class="amgdprcookie-link"><?= $escaper->escapeHtml(__('cookie policy')) ?></a>.</p> | |
| 26 | 26 | <div class="amgdprcookie-buttons-block"> | |
| 27 | Removed | <span class="amgdprcookie-button -allow" data-amcookie-js="allow" role="button" tabindex="0"><svg class="icon" viewBox="0 0 24 24"><path d="M4 12l5 5L20 6"/></svg></span> | |
| 28 | Removed | <span class="amgdprcookie-button -decline" data-amcookie-js="decline" role="button" tabindex="0"><svg class="icon" viewBox="0 0 24 24"><path d="M6 6l12 12M18 6L6 18"/></svg></span> | |
| 27 | Added | <button class="amgdprcookie-button -allow" data-amcookie-js="allow" type="button"><svg class="icon" viewBox="0 0 24 24"><path d="M4 12l5 5L20 6"/></svg></button> | |
| 28 | Added | <button class="amgdprcookie-button -decline" data-amcookie-js="decline" type="button"><svg class="icon" viewBox="0 0 24 24"><path d="M6 6l12 12M18 6L6 18"/></svg></button> | |
| 29 | 29 | </div> | |
| 30 | 30 | </div> | |
| 31 | 31 | </div> |
app/design/frontend/Validiscan/fixture/web/css/source/_theme.less Changes to app/design/frontend/Validiscan/fixture/web/css/source/_theme.less: 30 added, 0 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 1264 | 1264 | height: 18px; | |
| 1265 | 1265 | stroke-width: 3; | |
| 1266 | 1266 | } | |
| 1267 | Added | ||
| 1268 | Added | /* validiscan:button-semantics */ | |
| 1269 | Added | button.amgdprcookie-button.-allow, | |
| 1270 | Added | button.amgdprcookie-button.-decline { | |
| 1271 | Added | -webkit-appearance: none; | |
| 1272 | Added | appearance: none; | |
| 1273 | Added | margin: 0; | |
| 1274 | Added | padding: 0; | |
| 1275 | Added | box-shadow: none; | |
| 1276 | Added | font: inherit; | |
| 1277 | Added | letter-spacing: inherit; | |
| 1278 | Added | text-align: inherit; | |
| 1279 | Added | text-transform: inherit; | |
| 1280 | Added | text-shadow: none; | |
| 1281 | Added | } | |
| 1282 | Added | button.amgdprcookie-button.-allow:hover, | |
| 1283 | Added | button.amgdprcookie-button.-allow:focus, | |
| 1284 | Added | button.amgdprcookie-button.-allow:active { | |
| 1285 | Added | background: @nw-primary; | |
| 1286 | Added | border: 0; | |
| 1287 | Added | color: #fff; | |
| 1288 | Added | } | |
| 1289 | Added | button.amgdprcookie-button.-decline:hover, | |
| 1290 | Added | button.amgdprcookie-button.-decline:focus, | |
| 1291 | Added | button.amgdprcookie-button.-decline:active { | |
| 1292 | Added | background: #fff; | |
| 1293 | Added | border: 1px solid @nw-border; | |
| 1294 | Added | color: @nw-text; | |
| 1295 | Added | } | |
| 1296 | Added | /* /validiscan:button-semantics */ |
app/design/frontend/Validiscan/fixture/web/css/styles-m.css Changes to app/design/frontend/Validiscan/fixture/web/css/styles-m.css: 30 added, 0 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 1232 | 1232 | height: 18px; | |
| 1233 | 1233 | stroke-width: 3; | |
| 1234 | 1234 | } | |
| 1235 | Added | ||
| 1236 | Added | /* validiscan:button-semantics */ | |
| 1237 | Added | button.amgdprcookie-button.-allow, | |
| 1238 | Added | button.amgdprcookie-button.-decline { | |
| 1239 | Added | -webkit-appearance: none; | |
| 1240 | Added | appearance: none; | |
| 1241 | Added | margin: 0; | |
| 1242 | Added | padding: 0; | |
| 1243 | Added | box-shadow: none; | |
| 1244 | Added | font: inherit; | |
| 1245 | Added | letter-spacing: inherit; | |
| 1246 | Added | text-align: inherit; | |
| 1247 | Added | text-transform: inherit; | |
| 1248 | Added | text-shadow: none; | |
| 1249 | Added | } | |
| 1250 | Added | button.amgdprcookie-button.-allow:hover, | |
| 1251 | Added | button.amgdprcookie-button.-allow:focus, | |
| 1252 | Added | button.amgdprcookie-button.-allow:active { | |
| 1253 | Added | background: #1979c3; | |
| 1254 | Added | border: 0; | |
| 1255 | Added | color: #fff; | |
| 1256 | Added | } | |
| 1257 | Added | button.amgdprcookie-button.-decline:hover, | |
| 1258 | Added | button.amgdprcookie-button.-decline:focus, | |
| 1259 | Added | button.amgdprcookie-button.-decline:active { | |
| 1260 | Added | background: #fff; | |
| 1261 | Added | border: 1px solid #c2c2c2; | |
| 1262 | Added | color: #333; | |
| 1263 | Added | } | |
| 1264 | Added | /* /validiscan:button-semantics */ |
Magento: recipe icon-accessible-name
Show the before and after (2)
app/design/frontend/Validiscan/fixture/Magento_Search/templates/form.mini.phtml Changes to app/design/frontend/Validiscan/fixture/Magento_Search/templates/form.mini.phtml: 1 added, 1 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 20 | 20 | <div class="actions"> | |
| 21 | 21 | <!-- VALIDISCAN-SEEDED button-name: icon-only search button without text, title or aria-label --> | |
| 22 | 22 | <?php /* Fixed variant: <button type="submit" title="Search" class="action search"><span class="sr-only">Search</span>(icon, aria-hidden)</button>; the form gets role="search" aria-label="Site search". */ ?> | |
| 23 | Removed | <button type="submit" class="action search"><svg class="icon" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="M20 20l-4-4"/></svg></button> | |
| 23 | Added | <button type="submit" class="action search" aria-label="<?= $escaper->escapeHtmlAttr(__('Search')) ?>"><svg class="icon" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="M20 20l-4-4"/></svg></button> | |
| 24 | 24 | </div> | |
| 25 | 25 | </form> | |
| 26 | 26 | </div> |
app/design/frontend/Validiscan/fixture/Magento_Checkout/templates/cart/minicart.phtml Changes to app/design/frontend/Validiscan/fixture/Magento_Checkout/templates/cart/minicart.phtml: 1 added, 1 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 13 | 13 | ?> | |
| 14 | 14 | <!-- VALIDISCAN-SEEDED link-name + aria-valid-attr-value: the "My Cart" text was removed from the minicart link (only an icon and an empty counter remain) and aria-expanded carries the invalid value "closed" --> | |
| 15 | 15 | <div data-block="minicart" class="minicart-wrapper"> | |
| 16 | Removed | <a class="action showcart" href="<?= $escaper->escapeUrl($block->getShoppingCartUrl()) ?>" data-bind="scope: 'minicart_content'" aria-haspopup="true" aria-expanded="closed"> | |
| 16 | Added | <a class="action showcart" href="<?= $escaper->escapeUrl($block->getShoppingCartUrl()) ?>" data-bind="scope: 'minicart_content'" aria-haspopup="true" aria-expanded="closed" aria-label="<?= $escaper->escapeHtmlAttr(__('My Cart')) ?>"> | |
| 17 | 17 | <svg class="icon" viewBox="0 0 24 24"><path d="M3 4h2l2.4 11.2a2 2 0 0 0 2 1.6h7.7a2 2 0 0 0 2-1.5L21 8H6"/><circle cx="10" cy="20" r="1.5"/><circle cx="17" cy="20" r="1.5"/></svg> | |
| 18 | 18 | <span class="counter qty empty"><span class="counter-number"></span></span> | |
| 19 | 19 | </a> |
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