Checkout, address and login fields need labels, autocomplete and linked errors

The checkout is where the shop earns its money. Without labels, screen reader users do not know what to type; without autocomplete, people with motor or memory impairments must type their address by hand; and when errors are not tied to their fields, customers cannot find what went wrong and abandon the order.

Rule id
vs-forms-ecommerce
Source
Validiscan check
Impact
Critical
Fix tier
AUTO
on Shopify, Shopware, WooCommerce and Magento
Status
runs

What the check does

Checks checkout, address, account and login fields for missing autocomplete tokens (1.3.5), missing labels or instructions and required fields that are not indicated (3.3.2), and error messages that are not associated with their fields (3.3.1). Supersedes the axe rules label and autocomplete-valid.

On every page, the check looks at each input, select and textarea that assistive technology can perceive, except hidden and button-type inputs. A label counts from <label>, aria-label, aria-labelledby or title, not from a placeholder. Whether the label text describes the field is not tested.

A field’s purpose is guessed from its type, name, id, label text and placeholder. A recognized purpose with no autocomplete token, on, off or an invalid token is a finding, except off on a password field; a valid but different token is not.

An error message (role="alert" or an error class, beside one field) is reported when it is not inside a label and no aria-describedby or aria-errormessage references it; so is a field with aria-invalid="true" and no linked message. A required field (required or aria-required="true", checkboxes and radios aside) whose label shows no asterisk or required wording is reported, unless the form marks optional fields, states that all are required or requires every field. A field marked required only visually is reported too.

How to fix it

Give every field a visible <label> linked with for and id, add the matching autocomplete token (given-name, family-name, street-address, postal-code, email, tel, current-password and so on), mark required fields in the label and with the required attribute, and connect error messages to their fields with aria-describedby and aria-invalid="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.3.5 9.1.3.5 9.1.3.5
3.3.1 9.3.3.1 9.3.3.1
3.3.2 9.3.3.2 9.3.3.2

Where a recipe fixes it

Shopify: recipe autocomplete-tokens

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

sections/contact-form.liquid Changes to sections/contact-form.liquid: 1 added, 1 removed

Changes to sections/contact-form.liquid: 1 added, 1 removed
Line before Line after Change Code
14 14 {%- form 'contact', id: 'ContactForm', class: 'contact__form' %}
15 15 <div class="contact__fields">
16 16 <div class="field">
17 Removed <input class="field__input" type="text" name="contact[name]" placeholder="{{ 'templates.contact.form.name' | t }}">
17 Added <input class="field__input" type="text" name="contact[name]" placeholder="{{ 'templates.contact.form.name' | t }}" autocomplete="name">
18 18 </div>
19 19 <div class="field">
20 20 <input class="field__input" type="email" name="contact[email]" placeholder="{{ 'templates.contact.form.email' | t }} *" required>

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
11 11 {%- form 'customer', id: 'ContactFooter', class: 'footer__newsletter newsletter-form' -%}
12 12 <input type="hidden" name="contact[tags]" value="newsletter">
13 13 <div class="newsletter-form__field-wrapper">
14 Removed <input type="email" name="contact[email]" class="field__input" placeholder="{{ 'newsletter.label' | t }}" autocorrect="off" autocapitalize="off">
14 Added <input type="email" name="contact[email]" class="field__input" placeholder="{{ 'newsletter.label' | t }}" autocorrect="off" autocapitalize="off" autocomplete="email">
15 15 <button type="submit" class="button newsletter-form__button" name="commit">{{ 'newsletter.button' | t }}</button>
16 16 </div>
17 17 {%- endform -%}

sections/main-login.liquid Changes to sections/main-login.liquid: 1 added, 1 removed

Changes to sections/main-login.liquid: 1 added, 1 removed
Line before Line after Change Code
10 10 {%- form 'customer_login', novalidate: 'novalidate' %}
11 11 <div class="field">
12 12 <label class="field__label" for="CustomerEmail">{{ 'customer.login_page.email' | t }}</label>
13 Removed <input class="field__input" type="email" name="customer[email]" id="CustomerEmail" autocorrect="off" autocapitalize="off">
13 Added <input class="field__input" type="email" name="customer[email]" id="CustomerEmail" autocorrect="off" autocapitalize="off" autocomplete="email">
14 14 </div>
15 15 <div class="field">
16 16 <label class="field__label" for="CustomerPassword">{{ 'customer.login_page.password' | t }}</label>

snippets/checkout-field.liquid Changes to snippets/checkout-field.liquid: 4 added, 4 removed

Changes to snippets/checkout-field.liquid: 4 added, 4 removed
Line before Line after Change Code
15 15 <div class="field">
16 16 {%- case legacy %}
17 17 {%- when 'placeholder' %}
18 Removed <input class="field__input" type="{{ field_type }}" id="{{ field_id }}" name="{{ field_name }}" placeholder="{{ label }}"{% if required %} required{% endif %}>
18 Added <input class="field__input" type="{{ field_type }}" id="{{ field_id }}" name="{{ field_name }}" placeholder="{{ label }}"{% if required %} required{% endif %} autocomplete="{{ autocomplete }}">
19 19 {%- when 'starred' %}
20 20 <label class="field__label" for="{{ field_id }}">{{ label }} *</label>
21 Removed <input class="field__input" type="{{ field_type }}" id="{{ field_id }}" name="{{ field_name }}">
21 Added <input class="field__input" type="{{ field_type }}" id="{{ field_id }}" name="{{ field_name }}" autocomplete="{{ autocomplete }}">
22 22 {%- when 'off' %}
23 23 <label class="field__label" for="{{ field_id }}">{{ label }}</label>
24 Removed <input class="field__input" type="{{ field_type }}" id="{{ field_id }}" name="{{ field_name }}" autocomplete="off"{% if required %} required{% endif %}>
24 Added <input class="field__input" type="{{ field_type }}" id="{{ field_id }}" name="{{ field_name }}" autocomplete="{{ autocomplete }}"{% if required %} required{% endif %}>
25 25 {%- else %}
26 26 <label class="field__label" for="{{ field_id }}">{{ label }}</label>
27 Removed <input class="field__input" type="{{ field_type }}" id="{{ field_id }}" name="{{ field_name }}"{% if required %} required{% endif %}>
27 Added <input class="field__input" type="{{ field_type }}" id="{{ field_id }}" name="{{ field_name }}"{% if required %} required{% endif %} autocomplete="{{ autocomplete }}">
28 28 {%- endcase %}
29 29 </div>

Shopify: recipe label-association

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

sections/main-collection.liquid Changes to sections/main-collection.liquid: 2 added, 2 removed

Changes to sections/main-collection.liquid: 2 added, 2 removed
Line before Line after Change Code
64 64 <div class="collection__products">
65 65 <div class="collection__toolbar">
66 66 <div class="collection__sort">
67 Removed <label class="collection__sort-label">Sort by</label>
68 Removed <select name="sort_by" class="select__select" form="FacetFiltersForm">
67 Added <label class="collection__sort-label" for="SortBy-{{ section.id }}">Sort by</label>
68 Added <select name="sort_by" class="select__select" form="FacetFiltersForm" id="SortBy-{{ section.id }}">
69 69 <option value="manual" selected>Featured</option>
70 70 <option value="best-selling">Best selling</option>
71 71 <option value="price-ascending">Price, low to high</option>

sections/main-product.liquid Changes to sections/main-product.liquid: 2 added, 2 removed

Changes to sections/main-product.liquid: 2 added, 2 removed
Line before Line after Change Code
94 94 </div>
95 95 {%- else %}
96 96 <div class="product-form__input product-form__input--dropdown">
97 Removed <p class="form__label">{{ option.name | escape }}</p>
97 Added <p class="form__label" id="Options-{{ section.id }}-{{ forloop.index0 }}-label">{{ option.name | escape }}</p>
98 98 <div class="select">
99 Removed <select class="select__select" name="options[{{ option.name | escape }}]" data-option-index="{{ option_index }}">
99 Added <select class="select__select" name="options[{{ option.name | escape }}]" data-option-index="{{ option_index }}" aria-labelledby="Options-{{ section.id }}-{{ forloop.index0 }}-label">
100 100 {%- for value in option.values %}
101 101 <option value="{{ value | escape }}"{% if value == option.selected_value %} selected{% endif %}>{{ value | escape }}</option>
102 102 {%- endfor %}

Shopware: recipe autocomplete-tokens

Show the before and after (1)
adds tokens to the register, address, login, contact and newsletter fields; no autocomplete hits remain Example data

Resources/views/storefront/component/account/login.html.twig Changes to Resources/views/storefront/component/account/login.html.twig: 1 added, 1 removed

Changes to Resources/views/storefront/component/account/login.html.twig: 1 added, 1 removed
Line before Line after Change Code
19 19 name="username"
20 20 placeholder="{{ 'account.loginMailPlaceholder'|trans }}"
21 21 required
22 Removed autocomplete="off">
22 Added autocomplete="email">
23 23 </div>
24 24
25 25 <div class="form-group">

Resources/views/storefront/component/account/register.html.twig Changes to Resources/views/storefront/component/account/register.html.twig: 8 added, 4 removed

Changes to Resources/views/storefront/component/account/register.html.twig: 8 added, 4 removed
Line before Line after Change Code
33 33 id="{{ formId }}FirstName"
34 34 name="firstName"
35 35 placeholder="{{ 'account.firstNamePlaceholder'|trans }}"
36 Removed required>
36 Added required
37 Added autocomplete="given-name">
37 38 </div>
38 39 <div class="form-group col-sm-6">
39 40 <input type="text"
41 42 id="{{ formId }}LastName"
42 43 name="lastName"
43 44 placeholder="{{ 'account.lastNamePlaceholder'|trans }}"
44 Removed required>
45 Added required
46 Added autocomplete="family-name">
45 47 </div>
46 48 </div>
47 49 <div class="row g-2">
52 54 id="{{ formId }}Mail"
53 55 name="email"
54 56 placeholder="{{ 'account.emailPlaceholder'|trans }}"
55 Removed required>
57 Added required
58 Added autocomplete="email">
56 59 </div>
57 60 <div class="form-group col-sm-6">
58 61 <label class="form-label" for="{{ formId }}Password">{{ 'account.passwordLabel'|trans }}</label>
62 65 name="password"
63 66 minlength="8"
64 67 aria-describedby="{{ formId }}PasswordHint"
65 Removed required>
68 Added required
69 Added autocomplete="new-password">
66 70 <small class="form-text" id="{{ formId }}PasswordHint">{{ 'account.passwordHint'|trans }}</small>
67 71 </div>
68 72 </div>

Resources/views/storefront/component/address/address-form.html.twig Changes to Resources/views/storefront/component/address/address-form.html.twig: 8 added, 4 removed

Changes to Resources/views/storefront/component/address/address-form.html.twig: 8 added, 4 removed
Line before Line after Change Code
14 14 id="{{ prefix }}AddressStreet"
15 15 name="{{ prefix }}[street]"
16 16 placeholder="{{ 'address.streetPlaceholder'|trans }}"
17 Removed required>
17 Added required
18 Added autocomplete="address-line1">
18 19 </div>
19 20 {% endblock %}
20 21
26 27 name="{{ prefix }}[zipcode]"
27 28 placeholder="{{ 'address.zipcodePlaceholder'|trans }}"
28 29 inputmode="numeric"
29 Removed required>
30 Added required
31 Added autocomplete="postal-code">
30 32 </div>
31 33 {% endblock %}
32 34
37 39 class="form-control"
38 40 id="{{ prefix }}AddressCity"
39 41 name="{{ prefix }}[city]"
40 Removed required>
42 Added required
43 Added autocomplete="address-level2">
41 44 </div>
42 45 {% endblock %}
43 46
47 50 <select class="form-select"
48 51 id="{{ prefix }}AddressCountry"
49 52 name="{{ prefix }}[countryId]"
50 Removed required>
53 Added required
54 Added autocomplete="country">
51 55 {% for country in countries %}
52 56 <option value="{{ country.id }}"{% if loop.first %} selected{% endif %}>{{ country.name }}</option>
53 57 {% endfor %}

Resources/views/storefront/element/cms-element-form.html.twig Changes to Resources/views/storefront/element/cms-element-form.html.twig: 4 added, 4 removed

Changes to Resources/views/storefront/element/cms-element-form.html.twig: 4 added, 4 removed
Line before Line after Change Code
18 18 </select>
19 19 </div>
20 20 <div class="form-group col-md-4">
21 Removed <input type="text" class="form-control" id="contactFormFirstName" name="firstName" placeholder="{{ 'account.firstNamePlaceholder'|trans }}" required>
21 Added <input type="text" class="form-control" id="contactFormFirstName" name="firstName" placeholder="{{ 'account.firstNamePlaceholder'|trans }}" required autocomplete="given-name">
22 22 </div>
23 23 <div class="form-group col-md-4">
24 Removed <input type="text" class="form-control" id="contactFormLastName" name="lastName" placeholder="{{ 'account.lastNamePlaceholder'|trans }}" required>
24 Added <input type="text" class="form-control" id="contactFormLastName" name="lastName" placeholder="{{ 'account.lastNamePlaceholder'|trans }}" required autocomplete="family-name">
25 25 </div>
26 26 </div>
27 27
28 28 <div class="row g-2">
29 29 <div class="form-group col-md-6">
30 30 <label class="form-label" for="contactFormMail">{{ 'account.emailLabel'|trans }}</label>
31 Removed <input type="email" class="form-control" id="contactFormMail" name="email" placeholder="{{ 'account.emailPlaceholder'|trans }}" required>
31 Added <input type="email" class="form-control" id="contactFormMail" name="email" placeholder="{{ 'account.emailPlaceholder'|trans }}" required autocomplete="email">
32 32 </div>
33 33 <div class="form-group col-md-6">
34 34 <label class="form-label" for="contactFormPhone">{{ 'contact.phoneLabel'|trans }}</label>
35 Removed <input type="tel" class="form-control" id="contactFormPhone" name="phone">
35 Added <input type="tel" class="form-control" id="contactFormPhone" name="phone" autocomplete="tel">
36 36 </div>
37 37 </div>
38 38

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

Changes to Resources/views/storefront/layout/footer/footer.html.twig: 2 added, 1 removed
Line before Line after Change Code
62 62 id="footerNewsletterMail"
63 63 class="form-control"
64 64 placeholder="{{ 'footer.newsletterPlaceholder'|trans }}"
65 Removed required>
65 Added required
66 Added autocomplete="email">
66 67 <button type="submit" class="btn btn-primary newsletter-submit">{{ 'footer.newsletterSubmit'|trans }}</button>
67 68 </div>
68 69 <div class="form-check newsletter-privacy">

Shopware: recipe label-association

Show the before and after (1)
turns the variant group titles into labels of their selects; select-name no longer fires Example data

Resources/views/storefront/page/product-detail/configurator.html.twig Changes to Resources/views/storefront/page/product-detail/configurator.html.twig: 1 added, 1 removed

Changes to Resources/views/storefront/page/product-detail/configurator.html.twig: 1 added, 1 removed
Line before Line after Change Code
13 13 {% for group in product.configurator %}
14 14 {% if group.display == 'select' %}
15 15 <div class="product-detail-configurator-group">
16 Removed <div class="product-detail-configurator-group-title">{{ group.name }}</div>
16 Added <label class="product-detail-configurator-group-title" for="configurator-{{ group.id }}">{{ group.name }}</label>
17 17 <select class="form-select product-detail-configurator-select-input"
18 18 id="configurator-{{ group.id }}"
19 19 name="{{ group.id }}"{% if group.price|default(false) %}

WooCommerce: recipe autocomplete-tokens

Show the before and after (1)
sets the tokens of the login, registration and checkout fields of the fixture theme Example data

functions.php Changes to functions.php: 44 added, 0 removed

Changes to functions.php: 44 added, 0 removed
Line before Line after Change Code
308 308 * here; the remediation adds one for pages with the _kb_content_language meta and marks the
309 309 * English header and footer with lang="en-GB".
310 310 */
311 Added
312 Added /* validiscan:autocomplete-tokens */
313 Added /**
314 Added * Autocomplete tokens for the WooCommerce address fields, so browsers and assistive
315 Added * technology know what each field asks for (WCAG 1.3.5). Runs after the theme's own
316 Added * field filters.
317 Added *
318 Added * @param array $fields Checkout fields (grouped by section) or one address section.
319 Added * @return array
320 Added */
321 Added function kb_address_autocomplete( $fields ) {
322 Added $tokens = array(
323 Added 'first_name' => 'given-name',
324 Added 'last_name' => 'family-name',
325 Added 'company' => 'organization',
326 Added 'country' => 'country',
327 Added 'address_1' => 'address-line1',
328 Added 'address_2' => 'address-line2',
329 Added 'city' => 'address-level2',
330 Added 'state' => 'address-level1',
331 Added 'postcode' => 'postal-code',
332 Added 'phone' => 'tel',
333 Added 'email' => 'email',
334 Added );
335 Added foreach ( $fields as $key => $field ) {
336 Added if ( ! is_array( $field ) ) {
337 Added continue;
338 Added }
339 Added if ( 'billing' === $key || 'shipping' === $key ) {
340 Added $fields[ $key ] = kb_address_autocomplete( $field );
341 Added continue;
342 Added }
343 Added if ( ! preg_match( '/^(billing|shipping)_(.+)$/', (string) $key, $match ) || ! isset( $tokens[ $match[2] ] ) ) {
344 Added continue;
345 Added }
346 Added $fields[ $key ]['autocomplete'] = ( 'shipping' === $match[1] ? 'shipping ' : '' ) . $tokens[ $match[2] ];
347 Added unset( $fields[ $key ]['custom_attributes']['autocomplete'] );
348 Added }
349 Added return $fields;
350 Added }
351 Added add_filter( 'woocommerce_checkout_fields', 'kb_address_autocomplete', 99 );
352 Added add_filter( 'woocommerce_billing_fields', 'kb_address_autocomplete', 99 );
353 Added add_filter( 'woocommerce_shipping_fields', 'kb_address_autocomplete', 99 );
354 Added /* /validiscan:autocomplete-tokens */

woocommerce/myaccount/form-login.php Changes to woocommerce/myaccount/form-login.php: 3 added, 3 removed

Changes to woocommerce/myaccount/form-login.php: 3 added, 3 removed
Line before Line after Change Code
29 29
30 30 <p class="woocommerce-form-row woocommerce-form-row--wide form-row form-row-wide">
31 31 <label for="username"><?php esc_html_e( 'Username or email address', 'woocommerce' ); ?>&nbsp;<span class="required">*</span></label>
32 Removed <input type="text" class="woocommerce-Input woocommerce-Input--text input-text" name="username" id="username" autocomplete="off" value="<?php echo ( ! empty( $_POST['username'] ) ) ? esc_attr( wp_unslash( $_POST['username'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized ?>">
32 Added <input type="text" class="woocommerce-Input woocommerce-Input--text input-text" name="username" id="username" autocomplete="username" value="<?php echo ( ! empty( $_POST['username'] ) ) ? esc_attr( wp_unslash( $_POST['username'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized ?>">
33 33 </p>
34 34 <p class="woocommerce-form-row woocommerce-form-row--wide form-row form-row-wide">
35 35 <label for="password"><?php esc_html_e( 'Password', 'woocommerce' ); ?>&nbsp;<span class="required">*</span></label>
36 36 <span class="password-input">
37 Removed <input class="woocommerce-Input woocommerce-Input--text input-text" type="password" name="password" id="password" autocomplete="off" onpaste="return false;">
37 Added <input class="woocommerce-Input woocommerce-Input--text input-text" type="password" name="password" id="password" autocomplete="current-password" onpaste="return false;">
38 38 <button type="button" class="show-password-input"><?php kb_icon( 'eye' ); ?></button>
39 39 </span>
40 40 </p>
63 63 <?php do_action( 'woocommerce_register_form_start' ); ?>
64 64
65 65 <p class="woocommerce-form-row woocommerce-form-row--wide form-row form-row-wide">
66 Removed <input type="email" class="woocommerce-Input woocommerce-Input--text input-text" name="email" id="reg_email" autocomplete="off" value="" placeholder="<?php esc_attr_e( 'Email address *', 'kestrel-birch' ); ?>">
66 Added <input type="email" class="woocommerce-Input woocommerce-Input--text input-text" name="email" id="reg_email" autocomplete="email" value="" placeholder="<?php esc_attr_e( 'Email address *', 'kestrel-birch' ); ?>">
67 67 </p>
68 68 <p><?php esc_html_e( 'A link to set a new password will be sent to your email address.', 'woocommerce' ); ?></p>
69 69

WooCommerce: recipe label-association

Show the before and after (1)
ties an unassociated coupon label to its field, as in the fixed cart Example data

woocommerce/cart/cart.php Changes to woocommerce/cart/cart.php: 1 added, 1 removed

Changes to woocommerce/cart/cart.php: 1 added, 1 removed
Line before Line after Change Code
62 62 <tr>
63 63 <td colspan="6" class="actions">
64 64 <div class="coupon">
65 Removed <label class="screen-reader-text"><?php esc_html_e( 'Coupon:', 'woocommerce' ); ?></label>
65 Added <label class="screen-reader-text" for="coupon_code"><?php esc_html_e( 'Coupon:', 'woocommerce' ); ?></label>
66 66 <input type="text" name="coupon_code" class="input-text" id="coupon_code" value="" placeholder="<?php esc_attr_e( 'Coupon code', 'woocommerce' ); ?>">
67 67 <button type="submit" class="button" name="apply_coupon" value="<?php esc_attr_e( 'Apply coupon', 'woocommerce' ); ?>"><?php esc_html_e( 'Apply coupon', 'woocommerce' ); ?></button>
68 68 </div>

Magento: recipe autocomplete-tokens

Show the before and after (1)
adds tokens to the newsletter, login and contact forms and validates Example data

app/design/frontend/Validiscan/fixture/Magento_Contact/templates/form.phtml Changes to app/design/frontend/Validiscan/fixture/Magento_Contact/templates/form.phtml: 3 added, 3 removed

Changes to app/design/frontend/Validiscan/fixture/Magento_Contact/templates/form.phtml: 3 added, 3 removed
Line before Line after Change Code
21 21 <!-- VALIDISCAN-SEEDED vs-forms-ecommerce: name, email and phone lack autocomplete; required only via the CSS asterisk -->
22 22 <div class="field name required">
23 23 <label class="label" for="name"><span><?= $escaper->escapeHtml(__('Name')) ?></span></label>
24 Removed <div class="control"><input name="name" id="name" title="<?= $escaper->escapeHtmlAttr(__('Name')) ?>" value="<?= $escaper->escapeHtmlAttr($viewModel->getUserName()) ?>" class="input-text" type="text"></div>
24 Added <div class="control"><input name="name" id="name" title="<?= $escaper->escapeHtmlAttr(__('Name')) ?>" value="<?= $escaper->escapeHtmlAttr($viewModel->getUserName()) ?>" class="input-text" type="text" autocomplete="name"></div>
25 25 </div>
26 26 <div class="field email required">
27 27 <label class="label" for="email"><span><?= $escaper->escapeHtml(__('Email')) ?></span></label>
28 Removed <div class="control"><input name="email" id="email" title="<?= $escaper->escapeHtmlAttr(__('Email')) ?>" value="<?= $escaper->escapeHtmlAttr($viewModel->getUserEmail()) ?>" class="input-text" type="email"></div>
28 Added <div class="control"><input name="email" id="email" title="<?= $escaper->escapeHtmlAttr(__('Email')) ?>" value="<?= $escaper->escapeHtmlAttr($viewModel->getUserEmail()) ?>" class="input-text" type="email" autocomplete="email"></div>
29 29 </div>
30 30 <div class="field telephone">
31 31 <label class="label" for="telephone"><span><?= $escaper->escapeHtml(__('Phone Number')) ?></span></label>
32 Removed <div class="control"><input name="telephone" id="telephone" title="<?= $escaper->escapeHtmlAttr(__('Phone Number')) ?>" value="<?= $escaper->escapeHtmlAttr($viewModel->getUserTelephone()) ?>" class="input-text" type="tel"></div>
32 Added <div class="control"><input name="telephone" id="telephone" title="<?= $escaper->escapeHtmlAttr(__('Phone Number')) ?>" value="<?= $escaper->escapeHtmlAttr($viewModel->getUserTelephone()) ?>" class="input-text" type="tel" autocomplete="tel"></div>
33 33 </div>
34 34 <div class="field comment required">
35 35 <label class="label" for="comment"><span><?= $escaper->escapeHtml(__('What’s on your mind?')) ?></span></label>

app/design/frontend/Validiscan/fixture/Magento_Customer/templates/form/login.phtml Changes to app/design/frontend/Validiscan/fixture/Magento_Customer/templates/form/login.phtml: 2 added, 2 removed

Changes to app/design/frontend/Validiscan/fixture/Magento_Customer/templates/form/login.phtml: 2 added, 2 removed
Line before Line after Change Code
27 27 <div class="field email required">
28 28 <label class="label" for="email"><span><?= $escaper->escapeHtml(__('Email')) ?></span></label>
29 29 <div class="control">
30 Removed <input name="login[username]" value="<?= $escaper->escapeHtmlAttr($username) ?>" autocomplete="off" id="email" type="email" class="input-text mage-error" title="<?= $escaper->escapeHtmlAttr(__('Email')) ?>" aria-invalid="true" tabindex="1" onpaste="return false;" data-mage-init='{"mage/trim-input":{}}'>
30 Added <input name="login[username]" value="<?= $escaper->escapeHtmlAttr($username) ?>" autocomplete="email" id="email" type="email" class="input-text mage-error" title="<?= $escaper->escapeHtmlAttr(__('Email')) ?>" aria-invalid="true" tabindex="1" onpaste="return false;" data-mage-init='{"mage/trim-input":{}}'>
31 31 <div for="email" generated="true" class="mage-error" id="email-error"><?= $escaper->escapeHtml(__('Please enter a valid email address (Ex: johndoe@domain.com).')) ?></div>
32 32 </div>
33 33 </div>
35 35 <div class="field password required">
36 36 <label for="pass" class="label"><span><?= $escaper->escapeHtml(__('Password')) ?></span></label>
37 37 <div class="control">
38 Removed <input name="login[password]" type="password" autocomplete="off" class="input-text" id="pass" title="<?= $escaper->escapeHtmlAttr(__('Password')) ?>" tabindex="2" onpaste="return false;" oncopy="return false;">
38 Added <input name="login[password]" type="password" autocomplete="current-password" class="input-text" id="pass" title="<?= $escaper->escapeHtmlAttr(__('Password')) ?>" tabindex="2" onpaste="return false;" oncopy="return false;">
39 39 </div>
40 40 </div>
41 41 <!-- VALIDISCAN-SEEDED vs-accessible-auth: arithmetic CAPTCHA with no alternative -->

app/design/frontend/Validiscan/fixture/Magento_Newsletter/templates/subscribe.phtml Changes to app/design/frontend/Validiscan/fixture/Magento_Newsletter/templates/subscribe.phtml: 1 added, 1 removed

Changes to app/design/frontend/Validiscan/fixture/Magento_Newsletter/templates/subscribe.phtml: 1 added, 1 removed
Line before Line after Change Code
18 18 <!-- VALIDISCAN-SEEDED vs-forms-ecommerce (label): newsletter email only has a placeholder, no label and no autocomplete -->
19 19 <div class="field newsletter">
20 20 <div class="control">
21 Removed <input name="email" type="email" id="newsletter" placeholder="<?= $escaper->escapeHtmlAttr(__('Enter your email address')) ?>" data-mage-init='{"mage/trim-input":{}}' data-validate="{required:true, 'validate-email':true}">
21 Added <input name="email" type="email" id="newsletter" placeholder="<?= $escaper->escapeHtmlAttr(__('Enter your email address')) ?>" data-mage-init='{"mage/trim-input":{}}' data-validate="{required:true, 'validate-email':true}" autocomplete="email">
22 22 </div>
23 23 </div>
24 24 <div class="actions">

Magento: recipe label-association

Show the before and after (1)
turns the "Sort By" text into the sorter label and validates Example data

app/design/frontend/Validiscan/fixture/Magento_Catalog/templates/product/list/toolbar.phtml Changes to app/design/frontend/Validiscan/fixture/Magento_Catalog/templates/product/list/toolbar.phtml: 1 added, 1 removed

Changes to app/design/frontend/Validiscan/fixture/Magento_Catalog/templates/product/list/toolbar.phtml: 1 added, 1 removed
Line before Line after Change Code
12 12 <div class="toolbar-sorter sorter">
13 13 <!-- VALIDISCAN-SEEDED select-name + vs-forms-ecommerce: the "Sort By" text is a span, not a label for the select -->
14 14 <?php /* Fixed variant: <label class="sorter-label" for="sorter">Sort By</label>. */ ?>
15 Removed <span class="sorter-label"><?= $escaper->escapeHtml(__('Sort By')) ?></span>
15 Added <label class="sorter-label" for="sorter"><?= $escaper->escapeHtml(__('Sort By')) ?></label>
16 16 <select id="sorter" data-role="sorter" class="sorter-options">
17 17 <?php foreach ($block->getAvailableOrders() as $key => $order): ?>
18 18 <option value="<?= $escaper->escapeHtmlAttr($key) ?>"<?= $block->isOrderCurrent($key) ? ' selected' : '' ?>><?= $escaper->escapeHtml(__($order)) ?></option>

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