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
- 1.3.5 Identify Input Purpose, level AA
- 3.3.1 Error Identification, level A
- 3.3.2 Labels or Instructions, level A
EN 301 549 clauses
| 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)
sections/contact-form.liquid 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
| 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
| 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
| 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)
sections/main-collection.liquid 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
| 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)
Resources/views/storefront/component/account/login.html.twig 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
| 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
| 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
| 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
| 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)
Resources/views/storefront/page/product-detail/configurator.html.twig 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)
functions.php 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
| 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' ); ?> <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' ); ?> <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)
woocommerce/cart/cart.php 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)
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
| 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
| 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
| 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)
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
| 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 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