diff --git a/.changeset/true-shirts-shop.md b/.changeset/true-shirts-shop.md new file mode 100644 index 0000000000..b60df6eadb --- /dev/null +++ b/.changeset/true-shirts-shop.md @@ -0,0 +1,118 @@ +--- +"@bigcommerce/catalyst-core": patch +--- + +Improve accessibility for price displays by adding screen reader announcements for original prices, sale prices, and price ranges. Visual price elements are hidden from assistive technologies using `aria-hidden="true"` to prevent duplicate announcements, while visually hidden text provides context about pricing information. + +## Migration steps + +### Step 1: Update Cart Price Display + +Update `core/vibes/soul/sections/cart/client.tsx` to add accessibility labels for sale prices: + +```diff + {lineItem.salePrice && lineItem.salePrice !== lineItem.price ? ( + +- {lineItem.price} {lineItem.salePrice} ++ {t('originalPrice', { price: lineItem.price })} ++ {' '} ++ {t('currentPrice', { price: lineItem.salePrice })} ++ + + ) : ( + {lineItem.price} + )} +``` + +### Step 2: Update PriceLabel Component + +Update `core/vibes/soul/primitives/price-label/index.tsx` to add accessibility improvements for sale prices and price ranges: + +```diff + import { clsx } from 'clsx'; ++ import { useTranslations } from 'next-intl'; + + export function PriceLabel({ className, colorScheme = 'light', price }: Props) { ++ const t = useTranslations('Components.Price'); + + if (typeof price === 'string') { + return ( + ... + ); + } + + switch (price.type) { + case 'range': + return ( + +- {price.minValue} +-  –  +- {price.maxValue} ++ ++ {t('range', { minValue: price.minValue, maxValue: price.maxValue })} ++ ++ + + ); + + case 'sale': + return ( + ++ {t('originalPrice', { price: price.previousValue })} + {' '} ++ {t('currentPrice', { price: price.currentValue })} + + + ); + } + } +``` + +### Step 3: Add Translation Keys + +Update `core/messages/en.json` to include new translation keys for price accessibility: + +```diff + "Cart": { + "title": "Cart", + "heading": "Your cart", + "proceedToCheckout": "Proceed to checkout", + "increment": "Increase quantity", + "decrement": "Decrease quantity", + "removeItem": "Remove item", + "cartCombined": "We noticed you had items saved in a previous cart, so we've added them to your current cart for you.", + "cartRestored": "You started a cart on another device, and we've restored it here so you can pick up where you left off.", + "cartUpdateInProgress": "You have a cart update in progress. Are you sure you want to leave this page? Your changes may be lost.", ++ "originalPrice": "Original price was {price}.", ++ "currentPrice": "Current price is {price}.", +``` + +```diff + }, ++ "Price": { ++ "originalPrice": "Original price was {price}.", ++ "currentPrice": "Current price is {price}.", ++ "range": "Price from {minValue} to {maxValue}." ++ } + }, + "GiftCertificates": { +``` \ No newline at end of file diff --git a/core/messages/en.json b/core/messages/en.json index e8a99d5e6c..5a663c6a74 100644 --- a/core/messages/en.json +++ b/core/messages/en.json @@ -304,6 +304,8 @@ "cartCombined": "We noticed you had items saved in a previous cart, so we've added them to your current cart for you.", "cartRestored": "You started a cart on another device, and we've restored it here so you can pick up where you left off.", "cartUpdateInProgress": "You have a cart update in progress. Are you sure you want to leave this page? Your changes may be lost.", + "originalPrice": "Original price was {price}.", + "currentPrice": "Current price is {price}.", "CheckoutSummary": { "title": "Summary", "subTotal": "Subtotal", @@ -555,6 +557,11 @@ "description": "These cookies help us provide a better user experience and test new features." } } + }, + "Price": { + "originalPrice": "Original price was {price}.", + "currentPrice": "Current price is {price}.", + "range": "Price from {minValue} to {maxValue}." } }, "GiftCertificates": { diff --git a/core/vibes/soul/primitives/price-label/index.tsx b/core/vibes/soul/primitives/price-label/index.tsx index e329e55666..bc20ee1393 100644 --- a/core/vibes/soul/primitives/price-label/index.tsx +++ b/core/vibes/soul/primitives/price-label/index.tsx @@ -1,4 +1,5 @@ import { clsx } from 'clsx'; +import { useTranslations } from 'next-intl'; export interface PriceRange { type: 'range'; @@ -35,6 +36,8 @@ interface Props { * ``` */ export function PriceLabel({ className, colorScheme = 'light', price }: Props) { + const t = useTranslations('Components.Price'); + if (typeof price === 'string') { return ( - {price.minValue} -  –  - {price.maxValue} + + {t('range', { minValue: price.minValue, maxValue: price.maxValue })} + + ); case 'sale': return ( + {t('originalPrice', { price: price.previousValue })} {' '} + {t('currentPrice', { price: price.currentValue })}