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

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
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)
Before and after Example data

assets/base.css.liquid Changes to assets/base.css.liquid: 14 added, 0 removed

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

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)
buttons Example data

locales/en.default.json Changes to locales/en.default.json: 3 added, 1 removed

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

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

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

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

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

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>
links Example data

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

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

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

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 }}&amp;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)
turns the mouse-only slider dots into named buttons in their list items; vs-carousel stops reporting them Example data

Resources/app/storefront/src/scss/base.scss Changes to Resources/app/storefront/src/scss/base.scss: 17 added, 0 removed

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

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

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

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)
names every icon-only button of the home, product and cart pages; button-name no longer fires Example data

Resources/snippet/de_DE/kaffeekontor-nord.de-DE.json Changes to Resources/snippet/de_DE/kaffeekontor-nord.de-DE.json: 12 added, 0 removed

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

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

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

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

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

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

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

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>
names the logo and account links; social links whose network is data are left for review Example data

Resources/snippet/de_DE/kaffeekontor-nord.de-DE.json Changes to Resources/snippet/de_DE/kaffeekontor-nord.de-DE.json: 6 added, 0 removed

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

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

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

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)
turns the click-only cookie choices into buttons and names the close button Example data

assets/css/main.css Changes to assets/css/main.css: 17 added, 0 removed

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

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)
turns span controls of the cookie bar into buttons and validates Example data

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

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

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

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)
names the icon-only search button and validates Example data

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

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>
names the minicart link and leaves the product photo link for review Example data

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

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 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