Commit 6b09cf0927 for woocommerce
commit 6b09cf0927741bb11c1f4d993b202abfcbb2065c
Author: Peter Wilson <519727+peterwilsoncc@users.noreply.github.com>
Date: Tue Feb 10 01:34:03 2026 +1100
Introduce `summaryScreenReader` context to `cartItemPrice` filter. (#62080)
* Introduce `summaryScreenReader` context to `cartItemPrice` filter.
* Add changefile(s) from automation for the following project(s): woocommerce
* Validate filter return value in screen reader context.
* CS Fixes.
* Rename screen reader summary filter.
* Convert summary to Interpolate Element.
* Filter value must contain placeholders.
* CS Fix: Line length.
* Move createInterpolateElement to existing import.
* Replace price with placeholder.
* Add price to required placeholders.
* Update docblock following changes.
* Document new filter `modifyCartItemScreenReaderPrice`.
* Add changefile(s) from automation for the following project(s): woocommerce
* Use _n to demonstrate plurals.
* Typos, run on sentence.
* Add changefile(s) from automation for the following project(s): woocommerce
* Move screenshot back to original location.
* Add missed optional chaining to docs.
* Fix markdown tables
---------
Co-authored-by: Łukasz Strączyński <lukasz.straczynski@automattic.com>
diff --git a/docs/block-development/extensible-blocks/cart-and-checkout-blocks/filters-in-cart-and-checkout/order-summary-items.md b/docs/block-development/extensible-blocks/cart-and-checkout-blocks/filters-in-cart-and-checkout/order-summary-items.md
index ec272bf4c2..9e85c6adc4 100644
--- a/docs/block-development/extensible-blocks/cart-and-checkout-blocks/filters-in-cart-and-checkout/order-summary-items.md
+++ b/docs/block-development/extensible-blocks/cart-and-checkout-blocks/filters-in-cart-and-checkout/order-summary-items.md
@@ -10,6 +10,7 @@ The following Order Summary Items filters are available:
- `cartItemClass`
- `cartItemPrice`
+- `cartItemScreenReaderPrice`
- `itemName`
- `subtotalPriceFormat`
@@ -95,9 +96,9 @@ registerCheckoutFilters( 'example-extension', {
### Screenshots
-| Before | After |
-|:---------------------------------------------------------------------:|:---------------------------------------------------------------------:|
-| | |
+| Before | After |
+|:----------------------------------------------------------------------------------------------------------------------------------------------------:|:---------------------------------------------------------------------------------------------------------------------------------------------------:|
+|  |  |
## `cartItemPrice`
@@ -173,9 +174,98 @@ registerCheckoutFilters( 'example-extension', {
### Screenshots
-| Before | After |
-|:---------------------------------------------------------------------:|:---------------------------------------------------------------------:|
-| | |
+| Before | After |
+|:----------------------------------------------------------------------------------------------------------------------------------------------------:|:---------------------------------------------------------------------------------------------------------------------------------------------------:|
+|  |  |
+
+## `cartItemScreenReaderPrice`
+
+### Description
+
+The `cartItemScreenReaderPrice` filter allows for formatting the order summary item price announced to screen reader and assistive technology users. There are no visual changes on the screen. The code changes can be seen in the `<span class="screen-reader-text">` included for each item in the cart.
+
+### Parameters
+
+- _defaultValue_ `string` (default: `Total price for <quantity/> <productName/> item: <price/>` for purchases of a single item; `Total price for <quantity/> <productName/> items: <price/>` for purchases of multiple items) - The default order summary screen reader text.
+- _extensions_ `object` (default: `{}`) - The extensions object.
+- _args_ `object` - The arguments object with the following keys:
+ - _cart_ `object` - The cart object from `wc/store/cart`, see [Cart object](#cart-object).
+ - _cartItem_ `object` - The order summary item object from `wc/store/cart`, see [order summary item object](#cart-item-object).
+ - _context_ `string` (`summary`) - The context of the item, fixed to match the context of other filters in the mini-cart summary.
+- _validation_ `boolean` - Checks if the return value contains the substrings `<quantity/>`, `<productName/>` and `<price/>`.
+
+### Returns
+
+- `string` - The modified format of the order summary item price, which must contain the substrings `<quantity/>`, `<productName/>` and `<price/>`.
+
+### Code examples
+
+#### Basic example
+
+```tsx
+const { registerCheckoutFilters } = window.wc.blocksCheckout;
+const { _n } = window.wp.i18n;
+
+const modifyCartItemScreenReaderPrice = ( defaultValue, extensions, args, validation ) => {
+ const isOrderSummaryContext = args?.context === 'summary';
+
+ if ( ! isOrderSummaryContext ) {
+ return defaultValue;
+ }
+
+ return _n(
+ '<quantity/> <productName/> item will cost <price/>',
+ '<quantity/> <productName/> items will cost <price/>',
+ args?.cartItem?.quantity ?? 1,
+ 'example-extension'
+ );
+};
+
+registerCheckoutFilters( 'example-extension', {
+ cartItemScreenReaderPrice: modifyCartItemScreenReaderPrice,
+} );
+```
+
+#### Advanced example
+
+```tsx
+const { registerCheckoutFilters } = window.wc.blocksCheckout;
+const { _n } = window.wp.i18n;
+
+const modifyCartItemScreenReaderPrice = ( defaultValue, extensions, args, validation ) => {
+ const isOrderSummaryContext = args?.context === 'summary';
+
+ if ( ! isOrderSummaryContext ) {
+ return defaultValue;
+ }
+
+ if ( args?.cartItem?.name === 'Beanie with Logo' ) {
+ return _n(
+ 'Total price for <quantity/> <productName/> item: <price/> to keep you warm',
+ 'Total price for <quantity/> <productName/> items: <price/> to keep you warm',
+ args?.cartItem?.quantity ?? 1,
+ 'example-extension'
+ );
+ }
+
+ if ( args?.cartItem?.name === 'Sunglasses' ) {
+ return _n(
+ 'Total price for <quantity/> <productName/> item: <price/> to keep you cool',
+ 'Total price for <quantity/> <productName/> items: <price/> to keep you cool',
+ args?.cartItem?.quantity ?? 1,
+ 'example-extension'
+ );
+ }
+
+ return defaultValue;
+};
+
+registerCheckoutFilters( 'example-extension', {
+ cartItemScreenReaderPrice: modifyCartItemScreenReaderPrice,
+} );
+```
+
+> Filters can be also combined. See [Combined filters](/docs/block-development/extensible-blocks/cart-and-checkout-blocks/filters-in-cart-and-checkout/) for an example.
## `itemName`
@@ -250,9 +340,9 @@ registerCheckoutFilters( 'example-extension', {
### Screenshots
-| Before | After |
-|:---------------------------------------------------------------------:|:---------------------------------------------------------------------:|
-| | |
+| Before | After |
+|:----------------------------------------------------------------------------------------------------------------------------------------------:|:---------------------------------------------------------------------------------------------------------------------------------------------:|
+|  |  |
## `subtotalPriceFormat`
@@ -338,9 +428,9 @@ registerCheckoutFilters( 'example-extension', {
### Screenshots
-| Before | After |
-|:---------------------------------------------------------------------:|:---------------------------------------------------------------------:|
-| | |
+| Before | After |
+|:----------------------------------------------------------------------------------------------------------------------------------------------------------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------:|
+|  |  |
## Cart object
diff --git a/plugins/woocommerce/changelog/62080-fix-62009-filter-summary-screen-reader-text b/plugins/woocommerce/changelog/62080-fix-62009-filter-summary-screen-reader-text
new file mode 100644
index 0000000000..98fbaf2fef
--- /dev/null
+++ b/plugins/woocommerce/changelog/62080-fix-62009-filter-summary-screen-reader-text
@@ -0,0 +1,4 @@
+Significance: minor
+Type: enhancement
+
+Introduce `cartItemScreenReaderPrice` checkout filter.
\ No newline at end of file
diff --git a/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/order-summary/order-summary-item.tsx b/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/order-summary/order-summary-item.tsx
index 367153919c..abc30faa54 100644
--- a/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/order-summary/order-summary-item.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/order-summary/order-summary-item.tsx
@@ -12,11 +12,12 @@ import {
} from '@woocommerce/price-format';
import {
applyCheckoutFilter,
+ productPriceScreenReaderValidation,
productPriceValidation,
} from '@woocommerce/blocks-checkout';
import Dinero from 'dinero.js';
import { getSetting } from '@woocommerce/settings';
-import { useMemo } from '@wordpress/element';
+import { createInterpolateElement, useMemo } from '@wordpress/element';
import { useStoreCart } from '@woocommerce/base-context/hooks';
import { CartItem, isString } from '@woocommerce/types';
import { calculateSaleAmount } from '@woocommerce/base-utils';
@@ -133,6 +134,30 @@ const OrderSummaryItem = ( {
validation: productPriceValidation,
} );
+ /* translators: <quantity/>, <productName/> and <price/> are placeholders and should not be translated. */
+ const productPriceScreenReaderDefault = _n(
+ 'Total price for <quantity/> <productName/> item: <price/>',
+ 'Total price for <quantity/> <productName/> items: <price/>',
+ quantity,
+ 'woocommerce'
+ );
+
+ const productPriceScreenReaderFormat = applyCheckoutFilter( {
+ filterName: 'cartItemScreenReaderPrice',
+ defaultValue: productPriceScreenReaderDefault,
+ extensions,
+ arg,
+ validation: productPriceScreenReaderValidation,
+ } );
+
+ const ProductPriceScreenReaderOutput = () => {
+ return createInterpolateElement( productPriceScreenReaderFormat, {
+ quantity: <>{ quantity }</>,
+ productName: <>{ name }</>,
+ price: <>{ formatPrice( subtotalPrice, totalsCurrency ) }</>,
+ } );
+ };
+
const cartItemClassNameFilter = applyCheckoutFilter( {
filterName: 'cartItemClass',
defaultValue: '',
@@ -204,18 +229,7 @@ const OrderSummaryItem = ( {
<ProductMetadata { ...productMetaProps } />
</div>
<span className="screen-reader-text">
- { sprintf(
- /* translators: %1$d is the number of items, %2$s is the item name and %3$s is the total price including the currency symbol. */
- _n(
- 'Total price for %1$d %2$s item: %3$s',
- 'Total price for %1$d %2$s items: %3$s',
- quantity,
- 'woocommerce'
- ),
- quantity,
- name,
- formatPrice( subtotalPrice, totalsCurrency )
- ) }
+ <ProductPriceScreenReaderOutput />
</span>
<div
className="wc-block-components-order-summary-item__total-price"
diff --git a/plugins/woocommerce/client/blocks/packages/checkout/utils/validation/index.ts b/plugins/woocommerce/client/blocks/packages/checkout/utils/validation/index.ts
index ef7b30de5c..0d5a659eb5 100644
--- a/plugins/woocommerce/client/blocks/packages/checkout/utils/validation/index.ts
+++ b/plugins/woocommerce/client/blocks/packages/checkout/utils/validation/index.ts
@@ -4,4 +4,7 @@ export {
getFieldLabel,
} from './get-validity-message-for-input';
export { default as isPostcode } from './is-postcode';
-export { productPriceValidation } from './validate-filter';
+export {
+ productPriceScreenReaderValidation,
+ productPriceValidation,
+} from './validate-filter';
diff --git a/plugins/woocommerce/client/blocks/packages/checkout/utils/validation/validate-filter.ts b/plugins/woocommerce/client/blocks/packages/checkout/utils/validation/validate-filter.ts
index fddc286d2a..1afc53449e 100644
--- a/plugins/woocommerce/client/blocks/packages/checkout/utils/validation/validate-filter.ts
+++ b/plugins/woocommerce/client/blocks/packages/checkout/utils/validation/validate-filter.ts
@@ -5,3 +5,21 @@ import mustContain from './must-contain';
export const productPriceValidation = ( value: string ) =>
mustContain( value, '<price/>' );
+
+/**
+ * Ensure that the screen reader price text contains required placeholders.
+ *
+ * Ensure the filter value contains the three required placeholders:
+ * - <quantity/>
+ * - <productName/>
+ * - <price/>
+ */
+export const productPriceScreenReaderValidation = (
+ value: string
+): true | never => {
+ return (
+ mustContain( value, '<quantity/>' ) &&
+ mustContain( value, '<productName/>' ) &&
+ mustContain( value, '<price/>' )
+ );
+};