Deben anunciarse los cambios de precio, variante, existencias y carrito
Cuando un cliente elige una talla y cambian el precio o la disponibilidad, o cuando añade un producto al carrito, los clientes videntes ven el cambio. Quienes usan un lector de pantalla no oyen nada, así que no saben que el producto se ha añadido ni que la talla elegida está agotada.
- ID de la regla
- vs-live-updates
- Origen
- Comprobación de Validiscan
- Impacto
- Grave
- Nivel de corrección
- AUTO
- en Shopify, Shopware, WooCommerce y Magento
- Estado
- se ejecuta
Qué hace la comprobación
Detecta cambios de precio, variante, existencias y carrito en las páginas de producto y de carrito que se producen sin una región dinámica (live region), así como regiones aria-live que envuelven toda la página (4.1.3).
La comprobación se ejecuta solo en las páginas de producto y de carrito. Se notifica cada aria-live="polite" o "assertive" que haya en html, body, main o en un elemento que contenga main.
Con el envío de formularios bloqueado, la comprobación cambia un control situado fuera de la cabecera, la navegación, el pie de página y los cuadros de diálogo. Página de producto: el primer select de variante (cuyo nombre alude a la talla, el color o algo similar, o que está en la zona del producto); si no lo hay, un grupo de botones de opción de variante, y, si tampoco lo hay, botones de muestra con uno seleccionado. Página de carrito: el primer campo de cantidad, que cambia en una unidad.
Antes y después, lee los textos de hasta 120 caracteres cuyo itemprop, clase o id sugiere un precio, las existencias, el número de artículos del carrito o un total, y el botón de añadir al carrito; la página dispone de hasta 2 segundos para estabilizarse. Un texto visible que cambia se notifica, salvo que esté dentro de una región dinámica (live region) o la contenga (aria-live="polite" o "assertive", role="status", "alert", "log", output), o que otra región dinámica haya anunciado un cambio (en el caso de los precios y las existencias, una que contenga el nuevo valor). Un cambio que no se revierte al restablecer el control pasa a «pendiente de revisión», un estado que hoy no se convierte en hallazgo.
Cómo corregirlo
Añada una región dinámica pequeña y permanente (role="status" o aria-live="polite") para el precio, las existencias y el número de artículos del carrito, y actualice su texto cuando cambien la variante o el carrito. No ponga aria-live en contenedores grandes ni en toda la página, porque los lectores de pantalla vuelven a leerlo todo.
Criterios de conformidad de las WCAG
- 4.1.3 Status Messages, nivel AA
Apartados de la norma EN 301 549
| Criterio de las WCAG | Versión 3.2.1 | Versión 4.1.1 |
|---|---|---|
| 4.1.3 | 9.4.1.3 | 9.4.1.3 |
Dónde lo corrige una receta
Shopify: receta live-region-cart-variant
Mostrar el antes y el después (1)
sections/main-cart.liquid Cambios en sections/main-cart.liquid (líneas añadidas: 1; eliminadas: 1)
| Línea anterior | Línea nueva | Cambio | Código |
|---|---|---|---|
| 37 | 37 | ||
| 38 | 38 | <div class="cart__footer"> | |
| 39 | 39 | <h2 class="cart__summary-title">Order summary</h2> | |
| 40 | Eliminada | <p class="totals"> | |
| 40 | Añadida | <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 Cambios en sections/main-product.liquid (líneas añadidas: 1; eliminadas: 1)
| Línea anterior | Línea nueva | Cambio | Código |
|---|---|---|---|
| 71 | 71 | </div> | |
| 72 | 72 | {%- endif %} | |
| 73 | 73 | ||
| 74 | Eliminada | <div class="product__price" data-price-container> | |
| 74 | Añadida | <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: receta live-region-cart-variant
Mostrar el antes y el después (1)
Resources/views/storefront/page/checkout/cart/index.html.twig Cambios en Resources/views/storefront/page/checkout/cart/index.html.twig (líneas añadidas: 2; eliminadas: 2)
| Línea anterior | Línea nueva | Cambio | Código |
|---|---|---|---|
| 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 | Eliminada | <div class="line-item-total-price"> | |
| 62 | Añadida | <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 | Eliminada | <div class="checkout-aside-summary-totals" data-shipping-threshold="{{ shop.shippingThreshold }}" data-shipping-costs="{{ shop.shippingCosts }}"> | |
| 91 | Añadida | <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 Cambios en Resources/views/storefront/page/product-detail/buy-widget.html.twig (líneas añadidas: 1; eliminadas: 1)
| Línea anterior | Línea nueva | Cambio | Código |
|---|---|---|---|
| 17 | 17 | {% endblock %} | |
| 18 | 18 | ||
| 19 | 19 | {% block buy_widget_price %} | |
| 20 | Eliminada | <div class="product-detail-price-container"> | |
| 20 | Añadida | <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: receta live-region-cart-variant
Mostrar el antes y el después (2)
assets/js/theme.js Cambios en assets/js/theme.js (líneas añadidas: 15; eliminadas: 0)
| Línea anterior | Línea nueva | Cambio | Código |
|---|---|---|---|
| 255 | 255 | initCheckout(); | |
| 256 | 256 | initShowPassword(); | |
| 257 | 257 | })(); | |
| 258 | Añadida | ||
| 259 | Añadida | /* validiscan:live-region-cart-variant */ | |
| 260 | Añadida | // Announces cart changes in the status region next to the header cart (WCAG 4.1.3). | |
| 261 | Añadida | (function () { | |
| 262 | Añadida | const region = document.querySelector('[data-kb-cart-status]'); | |
| 263 | Añadida | if (!region || !window.jQuery) return; | |
| 264 | Añadida | window.jQuery(document.body).on('added_to_cart removed_from_cart', (event) => { | |
| 265 | Añadida | const message = region.getAttribute(event.type === 'added_to_cart' ? 'data-added' : 'data-removed'); | |
| 266 | Añadida | region.textContent = ''; | |
| 267 | Añadida | window.setTimeout(() => { | |
| 268 | Añadida | region.textContent = message || ''; | |
| 269 | Añadida | }, 100); | |
| 270 | Añadida | }); | |
| 271 | Añadida | })(); | |
| 272 | Añadida | /* /validiscan:live-region-cart-variant */ |
header.php Cambios en header.php (líneas añadidas: 1; eliminadas: 0)
| Línea anterior | Línea nueva | Cambio | Código |
|---|---|---|---|
| 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 | Añadida | <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 Cambios en woocommerce/single-product/add-to-cart/variable.php (líneas añadidas: 1; eliminadas: 1)
| Línea anterior | Línea nueva | Cambio | Código |
|---|---|---|---|
| 65 | 65 | </table> | |
| 66 | 66 | ||
| 67 | 67 | <div class="single_variation_wrap"> | |
| 68 | Eliminada | <div class="woocommerce-variation single_variation"> | |
| 68 | Añadida | <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: receta live-region-cart-variant
Mostrar el antes y el después (1)
app/design/frontend/Validiscan/fixture/Magento_Catalog/templates/product/price/final_price.phtml Cambios en app/design/frontend/Validiscan/fixture/Magento_Catalog/templates/product/price/final_price.phtml (líneas añadidas: 1; eliminadas: 1)
| Línea anterior | Línea nueva | Cambio | Código |
|---|---|---|---|
| 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 | Eliminada | <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 | Añadida | <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 Cambios en app/design/frontend/Validiscan/fixture/Magento_Catalog/templates/product/view/type/default.phtml (líneas añadidas: 1; eliminadas: 1)
| Línea anterior | Línea nueva | Cambio | Código |
|---|---|---|---|
| 11 | 11 | ?> | |
| 12 | 12 | <div class="product-info-stock-sku"> | |
| 13 | 13 | <?php if ($block->displayProductStockStatus()): ?> | |
| 14 | Eliminada | <div class="stock available" title="<?= $escaper->escapeHtmlAttr(__('Availability')) ?>"><span><?= $escaper->escapeHtml(__('In stock')) ?></span></div> | |
| 14 | Añadida | <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> |
Empiece con una revisión gratuita de su tienda.
Envíe la dirección de su tienda. Validiscan la escanea y le responde con cada fallo que el escaneo pueda demostrar, con la página y el código.
Solicitar revisión gratuitaO escriba a info@validiscan.com.
Qué cubre la revisión gratuita y qué deja fuera- Usted envía
- la dirección de su tienda
- Páginas escaneadas
- hasta 15
- Usted recibe
- todos los fallos que el escaneo puede demostrar, con la página y el código
- En su tienda
- solo páginas públicas; sin inicio de sesión, nada comprado, ningún formulario enviado
- Lo que no es
- una auditoría completa