Les mises à jour du prix, de la variante, du stock et du panier doivent être annoncées
Quand un client choisit une taille et que le prix ou la disponibilité change, ou quand il ajoute un produit au panier, les clients voyants voient la mise à jour. Les personnes qui utilisent un lecteur d’écran n’entendent rien : elles ne savent pas que le produit a été ajouté ni que la taille choisie est épuisée.
- Identifiant de la règle
- vs-live-updates
- Source
- Contrôle Validiscan
- Impact
- Grave
- Niveau de correction
- AUTO
- sur Shopify, Shopware, WooCommerce et Magento
- Statut
- exécutée
Ce que fait le contrôle
Détecte les changements de prix, de variante, de stock et de panier sur les fiches produit et les pages panier qui se produisent sans région live, ainsi que les régions aria-live qui englobent toute la page (4.1.3).
Le contrôle ne s’exécute que sur les fiches produit et les pages panier. Chaque aria-live="polite" ou "assertive" placé sur html, body, main ou sur un élément qui contient main est signalé.
Il modifie une commande située hors de l’en-tête, de la navigation, du pied de page et des boîtes de dialogue, en empêchant l’envoi du formulaire. Fiche produit : le premier select de variante (dont le nom évoque la taille, la couleur ou une notion voisine, ou qui se trouve dans la zone du produit), sinon un groupe de boutons radio de variante, sinon des boutons de pastille dont l’un est sélectionné. Page panier : le premier champ de quantité, modifié d’une unité.
Avant et après, il relève les textes de 120 caractères au plus dont l’attribut itemprop, la classe ou l’id évoque un prix, un stock, le nombre d’articles du panier ou un total, ainsi que le bouton d’ajout au panier ; la page dispose de 2 secondes au plus pour se stabiliser. Un texte visible qui change est signalé, sauf s’il se trouve dans une région live (aria-live="polite" ou "assertive", role="status", "alert", "log", output) ou s’il en contient une, ou si une autre région live a annoncé un changement (pour les prix et le stock, une région qui contient la nouvelle valeur). Un changement qui ne s’annule pas quand la commande est réinitialisée va dans « à examiner », ce qui ne produit aucun constat aujourd’hui.
Comment corriger
Ajoutez une petite région live permanente (role="status" ou aria-live="polite") pour le prix, le stock et le nombre d’articles du panier, et mettez à jour son texte quand la variante ou le panier change. Ne placez pas aria-live sur de grands conteneurs ni sur toute la page, ce qui amène les lecteurs d’écran à tout relire.
Critères de succès WCAG
- 4.1.3 Status Messages, niveau AA
Clauses de la norme EN 301 549
| Critère WCAG | Version 3.2.1 | Version 4.1.1 |
|---|---|---|
| 4.1.3 | 9.4.1.3 | 9.4.1.3 |
Où une recette corrige le problème
Shopify : recette live-region-cart-variant
Afficher l’avant et l’après (1)
sections/main-cart.liquid Modifications apportées à sections/main-cart.liquid (lignes ajoutées : 1, lignes supprimées : 1)
| Ligne avant | Ligne après | Modification | Code |
|---|---|---|---|
| 37 | 37 | ||
| 38 | 38 | <div class="cart__footer"> | |
| 39 | 39 | <h2 class="cart__summary-title">Order summary</h2> | |
| 40 | Supprimée | <p class="totals"> | |
| 40 | Ajoutée | <p class="totals" role="status"> | |
| 41 | 41 | <span class="totals__label">{{ 'sections.cart.subtotal' | t }}</span> | |
| 42 | 42 | <span class="totals__value" data-cart-subtotal>{{ cart.total_price | money_with_currency }}</span> | |
| 43 | 43 | </p> |
sections/main-product.liquid Modifications apportées à sections/main-product.liquid (lignes ajoutées : 1, lignes supprimées : 1)
| Ligne avant | Ligne après | Modification | Code |
|---|---|---|---|
| 71 | 71 | </div> | |
| 72 | 72 | {%- endif %} | |
| 73 | 73 | ||
| 74 | Supprimée | <div class="product__price" data-price-container> | |
| 74 | Ajoutée | <div class="product__price" data-price-container role="status"> | |
| 75 | 75 | {% render 'price', product: product, use_variant: true %} | |
| 76 | 76 | <p class="product__inventory" data-stock>{% if current_variant.available %}{{ 'products.product.in_stock' | t }}{% else %}{{ 'products.product.out_of_stock' | t }}{% endif %}</p> | |
| 77 | 77 | <p class="product__sku">{{ 'products.product.sku' | t }}: <span data-sku>{{ current_variant.sku }}</span></p> |
Shopware : recette live-region-cart-variant
Afficher l’avant et l’après (1)
Resources/views/storefront/page/checkout/cart/index.html.twig Modifications apportées à Resources/views/storefront/page/checkout/cart/index.html.twig (lignes ajoutées : 2, lignes supprimées : 2)
| Ligne avant | Ligne après | Modification | Code |
|---|---|---|---|
| 59 | 59 | <span class="line-item-unit-price-label">{{ 'checkout.cartHeaderUnitPrice'|trans }}:</span> | |
| 60 | 60 | <span class="line-item-unit-price-value">{{ item.unitPrice|currency }}*</span> | |
| 61 | 61 | </div> | |
| 62 | Supprimée | <div class="line-item-total-price"> | |
| 62 | Ajoutée | <div class="line-item-total-price" aria-live="polite" aria-atomic="true"> | |
| 63 | 63 | <span class="line-item-total-price-label">{{ 'checkout.cartHeaderTotalPrice'|trans }}:</span> | |
| 64 | 64 | <span class="line-item-total-price-value" data-line-total>{{ item.totalPrice|currency }}*</span> | |
| 65 | 65 | </div> | |
| 88 | 88 | <div class="col-lg-4 checkout-aside"> | |
| 89 | 89 | <div class="checkout-aside-summary"> | |
| 90 | 90 | <h2 class="checkout-aside-summary-header">{{ 'checkout.summaryHeader'|trans }}</h2> | |
| 91 | Supprimée | <div class="checkout-aside-summary-totals" data-shipping-threshold="{{ shop.shippingThreshold }}" data-shipping-costs="{{ shop.shippingCosts }}"> | |
| 91 | Ajoutée | <div class="checkout-aside-summary-totals" data-shipping-threshold="{{ shop.shippingThreshold }}" data-shipping-costs="{{ shop.shippingCosts }}" aria-live="polite" aria-atomic="true"> | |
| 92 | 92 | <dl class="checkout-aside-summary-list"> | |
| 93 | 93 | <dt class="checkout-aside-summary-label">{{ 'checkout.summaryPositionPrice'|trans }}</dt> | |
| 94 | 94 | <dd class="checkout-aside-summary-value" data-cart-subtotal>{{ cart.subtotal|currency }}*</dd> |
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)
| Ligne avant | Ligne après | Modification | Code |
|---|---|---|---|
| 17 | 17 | {% endblock %} | |
| 18 | 18 | ||
| 19 | 19 | {% block buy_widget_price %} | |
| 20 | Supprimée | <div class="product-detail-price-container"> | |
| 20 | Ajoutée | <div class="product-detail-price-container" aria-live="polite" aria-atomic="true"> | |
| 21 | 21 | <meta itemprop="priceCurrency" content="{{ shop.currency.isoCode }}"> | |
| 22 | 22 | <meta itemprop="price" content="{{ product.price }}"> | |
| 23 | 23 | <p class="product-detail-price{% if product.listPrice %} with-list-price{% endif %}" data-price-display>{{ product.price|currency }}*</p> |
WooCommerce : recette live-region-cart-variant
Afficher l’avant et l’après (2)
assets/js/theme.js Modifications apportées à assets/js/theme.js (lignes ajoutées : 15, lignes supprimées : 0)
| Ligne avant | Ligne après | Modification | Code |
|---|---|---|---|
| 255 | 255 | initCheckout(); | |
| 256 | 256 | initShowPassword(); | |
| 257 | 257 | })(); | |
| 258 | Ajoutée | ||
| 259 | Ajoutée | /* validiscan:live-region-cart-variant */ | |
| 260 | Ajoutée | // Announces cart changes in the status region next to the header cart (WCAG 4.1.3). | |
| 261 | Ajoutée | (function () { | |
| 262 | Ajoutée | const region = document.querySelector('[data-kb-cart-status]'); | |
| 263 | Ajoutée | if (!region || !window.jQuery) return; | |
| 264 | Ajoutée | window.jQuery(document.body).on('added_to_cart removed_from_cart', (event) => { | |
| 265 | Ajoutée | const message = region.getAttribute(event.type === 'added_to_cart' ? 'data-added' : 'data-removed'); | |
| 266 | Ajoutée | region.textContent = ''; | |
| 267 | Ajoutée | window.setTimeout(() => { | |
| 268 | Ajoutée | region.textContent = message || ''; | |
| 269 | Ajoutée | }, 100); | |
| 270 | Ajoutée | }); | |
| 271 | Ajoutée | })(); | |
| 272 | Ajoutée | /* /validiscan:live-region-cart-variant */ |
header.php Modifications apportées à header.php (lignes ajoutées : 1, lignes supprimées : 0)
| Ligne avant | Ligne après | Modification | Code |
|---|---|---|---|
| 67 | 67 | <?php kb_icon( 'bag' ); ?><span class="kb-cart-amount"><?php echo wp_kses_post( WC()->cart->get_cart_subtotal() ); ?></span> | |
| 68 | 68 | <span class="count"><?php echo esc_html( sprintf( _n( '%d item', '%d items', WC()->cart->get_cart_contents_count(), 'storefront' ), WC()->cart->get_cart_contents_count() ) ); ?></span> | |
| 69 | 69 | </a> | |
| 70 | Ajoutée | <span class="screen-reader-text" role="status" data-kb-cart-status data-added="<?php esc_attr_e( 'Product added to your cart.', 'kestrel-birch' ); ?>" data-removed="<?php esc_attr_e( 'Product removed from your cart.', 'kestrel-birch' ); ?>"></span> | |
| 70 | 71 | </li> | |
| 71 | 72 | </ul> | |
| 72 | 73 | </div> |
woocommerce/single-product/add-to-cart/variable.php Modifications apportées à woocommerce/single-product/add-to-cart/variable.php (lignes ajoutées : 1, lignes supprimées : 1)
| Ligne avant | Ligne après | Modification | Code |
|---|---|---|---|
| 65 | 65 | </table> | |
| 66 | 66 | ||
| 67 | 67 | <div class="single_variation_wrap"> | |
| 68 | Supprimée | <div class="woocommerce-variation single_variation"> | |
| 68 | Ajoutée | <div class="woocommerce-variation single_variation" aria-live="polite" aria-atomic="true"> | |
| 69 | 69 | <div class="woocommerce-variation-description"></div> | |
| 70 | 70 | <div class="woocommerce-variation-price"><span class="price"><?php echo wp_kses_post( wc_price( $kb_selected['display_price'] ) ); ?></span></div> | |
| 71 | 71 | <div class="woocommerce-variation-availability"><?php echo wp_kses_post( $kb_selected['availability_html'] ); ?></div> |
Magento : recette live-region-cart-variant
Afficher l’avant et l’après (1)
app/design/frontend/Validiscan/fixture/Magento_Catalog/templates/product/price/final_price.phtml Modifications apportées à app/design/frontend/Validiscan/fixture/Magento_Catalog/templates/product/price/final_price.phtml (lignes ajoutées : 1, lignes supprimées : 1)
| Ligne avant | Ligne après | Modification | Code |
|---|---|---|---|
| 13 | 13 | // Fixed variant: the product-page box carries aria-live="polite" aria-atomic="true" and the stock | |
| 14 | 14 | // message (product/view/type/default.phtml) is role="status". | |
| 15 | 15 | ?> | |
| 16 | Supprimée | <div class="price-box price-final_price" data-role="priceBox" data-product-id="<?= (int) $product->getId() ?>" data-price-box="product-id-<?= (int) $product->getId() ?>"<?php if ($isProductPage): ?> data-base-price="<?= (float) $amount ?>"<?php endif; ?>> | |
| 16 | Ajoutée | <div class="price-box price-final_price" data-role="priceBox" data-product-id="<?= (int) $product->getId() ?>" data-price-box="product-id-<?= (int) $product->getId() ?>"<?php if ($isProductPage): ?> data-base-price="<?= (float) $amount ?>"<?php endif; ?> aria-live="polite" aria-atomic="true"> | |
| 17 | 17 | <span class="normal-price"><span class="price-container price-final_price tax weee"><span id="product-price-<?= (int) $product->getId() ?>" data-price-amount="<?= (float) $amount ?>" data-price-type="finalPrice" class="price-wrapper"><span class="price"><?= $escaper->escapeHtml($block->renderAmount($block->getPriceType('final_price')->getAmount(), ['display_label' => false])) ?></span></span></span></span> | |
| 18 | 18 | </div> |
app/design/frontend/Validiscan/fixture/Magento_Catalog/templates/product/view/type/default.phtml Modifications apportées à app/design/frontend/Validiscan/fixture/Magento_Catalog/templates/product/view/type/default.phtml (lignes ajoutées : 1, lignes supprimées : 1)
| Ligne avant | Ligne après | Modification | Code |
|---|---|---|---|
| 11 | 11 | ?> | |
| 12 | 12 | <div class="product-info-stock-sku"> | |
| 13 | 13 | <?php if ($block->displayProductStockStatus()): ?> | |
| 14 | Supprimée | <div class="stock available" title="<?= $escaper->escapeHtmlAttr(__('Availability')) ?>"><span><?= $escaper->escapeHtml(__('In stock')) ?></span></div> | |
| 14 | Ajoutée | <div class="stock available" title="<?= $escaper->escapeHtmlAttr(__('Availability')) ?>" role="status"><span><?= $escaper->escapeHtml(__('In stock')) ?></span></div> | |
| 15 | 15 | <?php endif; ?> | |
| 16 | 16 | <div class="product attribute sku"><strong class="type"><?= $escaper->escapeHtml(__('SKU')) ?></strong> <div class="value" itemprop="sku"><?= $escaper->escapeHtml($product->getSku()) ?></div></div> | |
| 17 | 17 | </div> |
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 gratuiteOu é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