L’ordre de tabulation doit suivre l’ordre visuel

Quand la touche Tab saute de l’en-tête au pied de page puis revient à un produit, les personnes qui naviguent au clavier ou avec un lecteur d’écran perdent le fil, manquent des options et ont du mal à remplir les formulaires dans le bon ordre.

Identifiant de la règle
vs-focus-order
Source
Contrôle Validiscan
Impact
Modéré
Niveau de correction
MANUAL
sur Shopify, Shopware, WooCommerce et Magento
Statut
exécutée

Ce que fait le contrôle

Compare la séquence de tabulation avec la disposition visuelle et signale les sauts où le focus avance dans un ordre qui ne correspond pas à l’écran, par exemple à cause de valeurs tabindex positives ou de contenus repositionnés en CSS (2.4.3).

Le contrôle recense jusqu’à 300 éléments visibles et atteignables au clavier, avec leur position sur la page. Il déduit l’ordre de tabulation du balisage au lieu d’appuyer sur Tab : d’abord les valeurs positives de tabindex, par ordre croissant, puis l’ordre du document. Le contrôle s’exécute dans chaque zone d’affichage scannée, par défaut de 1440 et de 390 px de large.

Tout élément doté d’un tabindex positif fait l’objet d’un constat. Pour les passages entre éléments qui n’en ont pas, le contrôle compare chaque élément à celui qui le précède dans l’ordre de tabulation. Quand le bord inférieur d’un élément se situe à plus de 100 px au-dessus du bord supérieur de l’élément précédent, le focus remonte dans la page, et le passage va dans « à examiner », 25 au plus par page. Les règles CSS qui réorganisent le contenu, comme order en flexbox, le placement dans une grille ou le positionnement absolu, peuvent provoquer un tel saut, et une personne doit décider si l’ordre garde un sens. Le scanner ne transforme pas aujourd’hui les résultats « à examiner » en constats : seules les valeurs positives de tabindex produisent donc des constats. Les sauts latéraux ou vers le bas de la page ne sont pas mesurés.

Comment corriger

Faites correspondre l’ordre du code source des modèles à l’ordre visuel, supprimez les valeurs tabindex positives et évitez de réorganiser le contenu avec CSS order, flex-direction ou le positionnement absolu lorsque cela modifie l’ordre dans lequel on lit la page et on la parcourt avec Tab.

Critères de succès WCAG

Clauses de la norme EN 301 549

La clause de la norme EN 301 549 pour chaque critère, selon la version de la norme
Critère WCAG Version 3.2.1 Version 4.1.1
2.4.3 9.2.4.3 9.2.4.3

Recettes qui énumèrent cette règle

Cette règle a le niveau MANUAL. Dans un projet, un constat de cette règle suit la voie MANUAL, et ces recettes ne lui sont pas appliquées.

Shopify : recette remove-positive-tabindex

Afficher l’avant et l’après (1)
Avant et après Données d’exemple

sections/header.liquid Modifications apportées à sections/header.liquid (lignes ajoutées : 1, lignes supprimées : 1)

Modifications apportées à sections/header.liquid (lignes ajoutées : 1, lignes supprimées : 1)
Ligne avant Ligne après Modification Code
37 37
38 38 <div class="header__search">
39 39 <form action="{{ routes.search_url }}" method="get" role="search" class="search">
40 Supprimée <input class="search__input field__input" type="search" name="q" value="" placeholder="{{ 'general.search.placeholder' | t }}" tabindex="1">
40 Ajoutée <input class="search__input field__input" type="search" name="q" value="" placeholder="{{ 'general.search.placeholder' | t }}">
41 41 <button type="submit" class="search__button">
42 42 {%- render 'icon', icon: 'search', size: 20 -%}
43 43 </button>

Shopware : recette remove-positive-tabindex

Afficher l’avant et l’après (1)
removes tabindex="1" from the buy button; vs-focus-order and axe tabindex stop firing Données d’exemple

Resources/views/storefront/page/product-detail/buy-widget.html.twig Modifications apportées à Resources/views/storefront/page/product-detail/buy-widget.html.twig (lignes ajoutées : 1, lignes supprimées : 1)

Modifications apportées à Resources/views/storefront/page/product-detail/buy-widget.html.twig (lignes ajoutées : 1, lignes supprimées : 1)
Ligne avant Ligne après Modification Code
82 82 </div>
83 83 <div class="buy-widget-submit">
84 84 <button class="btn btn-primary btn-buy"
85 Supprimée title="{{ 'detail.addProduct'|trans }}" tabindex="1">
85 Ajoutée title="{{ 'detail.addProduct'|trans }}">
86 86 {{ 'detail.addProduct'|trans }}
87 87 </button>
88 88 </div>

WooCommerce : recette remove-positive-tabindex

Afficher l’avant et l’après (1)
removes tabindex="1" from the header search field of the fixture theme Données d’exemple

header.php Modifications apportées à header.php (lignes ajoutées : 1, lignes supprimées : 1)

Modifications apportées à header.php (lignes ajoutées : 1, lignes supprimées : 1)
Ligne avant Ligne après Modification Code
49 49 <form role="search" method="get" class="woocommerce-product-search" action="<?php echo esc_url( home_url( '/search' ) ); ?>">
50 50 <label class="screen-reader-text" for="woocommerce-product-search-field-0"><?php esc_html_e( 'Search for:', 'woocommerce' ); ?></label>
51 51 <?php // VALIDISCAN-SEED vs-focus-order + tabindex: tabindex="1" "so the search is always the first stop". ?>
52 Supprimée <input type="search" id="woocommerce-product-search-field-0" class="search-field" placeholder="<?php echo esc_attr__( 'Search products&hellip;', 'woocommerce' ); ?>" value="<?php echo get_search_query(); ?>" name="s" tabindex="1">
52 Ajoutée <input type="search" id="woocommerce-product-search-field-0" class="search-field" placeholder="<?php echo esc_attr__( 'Search products&hellip;', 'woocommerce' ); ?>" value="<?php echo get_search_query(); ?>" name="s">
53 53 <?php // VALIDISCAN-SEED button-name: icon-only submit button without text. ?>
54 54 <button type="submit" value="<?php echo esc_attr_x( 'Search', 'submit button', 'woocommerce' ); ?>" class="kb-search-submit"><?php kb_icon( 'search' ); ?></button>
55 55 <input type="hidden" name="post_type" value="product">

Magento : recette remove-positive-tabindex

Afficher l’avant et l’après (1)
removes tabindex 1-4 from the login form and the CAPTCHA and validates Données d’exemple

app/design/frontend/Validiscan/fixture/Magento_Customer/templates/form/captcha-math.phtml Modifications apportées à app/design/frontend/Validiscan/fixture/Magento_Customer/templates/form/captcha-math.phtml (lignes ajoutées : 1, lignes supprimées : 1)

Modifications apportées à app/design/frontend/Validiscan/fixture/Magento_Customer/templates/form/captcha-math.phtml (lignes ajoutées : 1, lignes supprimées : 1)
Ligne avant Ligne après Modification Code
14 14 <div class="field captcha required" role="group" aria-labelledby="captcha-question" data-captcha-type="math">
15 15 <label for="captcha_user_login" class="label" id="captcha-question"><span><?= $escaper->escapeHtml(__('Security question: what is %1 + %2?', $a, $b)) ?></span></label>
16 16 <div class="control captcha">
17 Supprimée <input name="captcha[user_login]" type="text" class="input-text required-entry" id="captcha_user_login" autocomplete="off" inputmode="numeric" tabindex="3" data-captcha-answer-hash="<?= $escaper->escapeHtmlAttr(md5((string) ($a + $b))) ?>">
17 Ajoutée <input name="captcha[user_login]" type="text" class="input-text required-entry" id="captcha_user_login" autocomplete="off" inputmode="numeric" data-captcha-answer-hash="<?= $escaper->escapeHtmlAttr(md5((string) ($a + $b))) ?>">
18 18 </div>
19 19 </div>

app/design/frontend/Validiscan/fixture/Magento_Customer/templates/form/login.phtml Modifications apportées à app/design/frontend/Validiscan/fixture/Magento_Customer/templates/form/login.phtml (lignes ajoutées : 3, lignes supprimées : 3)

Modifications apportées à app/design/frontend/Validiscan/fixture/Magento_Customer/templates/form/login.phtml (lignes ajoutées : 3, lignes supprimées : 3)
Ligne avant Ligne après Modification 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 Supprimée <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 Ajoutée <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" 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 Supprimée <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 Ajoutée <input name="login[password]" type="password" autocomplete="off" class="input-text" id="pass" title="<?= $escaper->escapeHtmlAttr(__('Password')) ?>" onpaste="return false;" oncopy="return false;">
39 39 </div>
40 40 </div>
41 41 <!-- VALIDISCAN-SEEDED vs-accessible-auth: arithmetic CAPTCHA with no alternative -->
42 42 <?= $block->getChildHtml('form_additional_info') /* math CAPTCHA module: div.field.captcha, input#captcha_user_login tabindex="3" */ ?>
43 43 <div class="actions-toolbar">
44 Supprimée <div class="primary"><button type="submit" class="action login primary" name="send" id="send2" tabindex="4"><span><?= $escaper->escapeHtml(__('Sign In')) ?></span></button></div>
44 Ajoutée <div class="primary"><button type="submit" class="action login primary" name="send" id="send2"><span><?= $escaper->escapeHtml(__('Sign In')) ?></span></button></div>
45 45 <div class="secondary"><a class="action remind" href="<?= $escaper->escapeUrl($block->getUrl('contact')) ?>"><span><?= $escaper->escapeHtml(__('Forgot Your Password? Contact us')) ?></span></a></div>
46 46 </div>
47 47 </fieldset>

Commencez par une vérification gratuite de votre boutique.

Envoyez l’adresse de votre boutique. Validiscan la scanne et vous envoie chaque échec que le scan peut prouver, avec sa page et son code.

Demander une vérification gratuite

Ou écrivez à info@validiscan.com.

Ce que couvre la vérification gratuite, et ce qu’elle laisse de côté
Vous envoyez
l’adresse de votre boutique
Pages scannées
jusqu’à 15
Vous recevez
chaque échec que le scan peut prouver, avec la page et le code
Sur votre boutique
pages publiques uniquement ; aucune connexion, aucun achat, aucun formulaire envoyé
Ce qu’elle n’est pas
un audit complet

Écrire à Validiscan

Pour une vérification gratuite, indiquez l’adresse de votre boutique et sa plateforme.

Validiscan utilise ce que vous envoyez pour vous répondre. Politique de confidentialité