Communicating Prices and Discounts Accessibly: del, ins and Discount Badges
AI generated
A11Y
WCAG
Accessibility · Price Display · Discounts
Accessible Price Display
del/ins semantics for old prices, text alternative discount badges, and understandable price ranges

A struck through old price next to a highlighted new price is one of the strongest visual signals in e-commerce, but plain CSS strikethrough via text-decoration stays completely invisible to screen reader users. Using the semantic elements del and ins correctly instead, giving discount badges a text alternative, and phrasing price ranges for product variants clearly turns a purely visual price treatment into information every user can reliably retrieve.

9 min read del/ins instead of plain CSS strikethrough Discount badge with text alternative

1. Why plain CSS strikethrough is not enough for old prices

An old price is usually struck through visually via text-decoration: line-through on an arbitrary span element. This CSS property is a pure style declaration with no semantic meaning at all, it changes neither the role nor the accessible name of the element in the accessibility tree. A screen reader therefore reads out the struck through price exactly the same way as the new price, with no indication whatsoever that it represents a now invalid, earlier value.

That loses exactly the information that makes a reduced price recognizable as a discount in the first place: the comparison between an old and a new value. For sighted users this comparison is immediately obvious through the strikethrough line, for screen reader users without extra markup all that remains is a confusing sequence of two price figures with no recognizable relationship between them.

2. The correct HTML elements: del for the old price, ins for the new price

HTML provides exactly the semantic elements meant for this use case with del and ins: del marks content removed from the document or made invalid, ins marks newly inserted content. A reduced price can be marked up accordingly with del for the original price and ins for the current, reduced price, which semantically captures exactly the relationship between the two values.

Many browsers already render del with a strikethrough line by default and ins underlined by default, so using these elements can even save additional CSS for the pure visualization. It still matters to explicitly define the styling yourself instead of relying on browser defaults, since those can vary by browser and by user stylesheet.


<p class="price">
  <del class="price__old">79.99 EUR</del>
  <ins class="price__new">59.99 EUR</ins>
</p>

<style>
.price__old { text-decoration: line-through; color: #71717a; }
.price__new { text-decoration: none; font-weight: 600; color: #18181b; }
</style>

3. How screen readers actually handle del and ins in practice

In practice, not every screen reader automatically announces the semantic meaning of del and ins as audible extra information, some simply read the content as plain text without mentioning "struck through" or "inserted". The correct elements are still the right foundation, since they at least carry the correct semantic role in the accessibility tree and get used correctly by assistive technologies that do support this announcement.

For reliable results across every screen reader, an additional aria-label or a visible, summarizing text is recommended, one that captures the entire price comparison in a single sentence, regardless of whether the individual assistive technology reads out del and ins or not. This redundant safeguard is the same principle already used to secure reliable announcements for the star rating and the wishlist toggle.

4. Practical pattern: visible price plus hidden context text

The most robust pattern combines del and ins with an additional, visually hidden sentence that summarizes the entire price comparison in natural language: "Instead of 79.99 euros, now 59.99 euros, you save 25 percent." This sentence is placed as an sr-only element directly next to or inside the price container and delivers a complete, understandable announcement regardless of how a given screen reader handles del/ins.

This pattern matters especially for product listings with many reduced items, since a user quickly scanning through the list would otherwise hear two isolated numbers with no context multiple times, instead of a clear, self contained statement for every item.


<p class="price">
  <span class="sr-only">Instead of 79.99 euros, now 59.99 euros, you save 25 percent.</span>
  <del class="price__old" aria-hidden="true">79.99 EUR</del>
  <ins class="price__new" aria-hidden="true">59.99 EUR</ins>
</p>

5. Marking up percentage discount badges with a text alternative

A discount badge often shows only a short figure like "-25%" in a colored circle or rectangle at the top left of the product image. Without accompanying text it stays unclear to a screen reader what this figure refers to, it could theoretically also mean a rating, a stock quantity, or something else. An aria-label like "25 percent discount" directly on the badge element resolves this ambiguity unambiguously.

If the badge is purely decorative and the actual discount information already exists in full as text elsewhere, for example in the price comparison from the previous section, the badge can instead be removed from the accessibility tree with aria-hidden="true" to avoid duplicate announcements. Which of the two techniques fits depends on whether the badge is the sole source of the discount information or merely a visual repetition.


<span class="discount-badge" aria-label="25 percent discount">-25%</span>

6. Announcing price ranges for product variants clearly and dynamically

Configurable products with multiple variants often display a price range like "from 19.99 euros" as long as no specific variant has been selected yet. This text should always include the word "from" or an equivalent phrasing, rather than displaying only the lowest figure in isolation, otherwise it creates the impression of a fixed price instead of a range.

Once the user selects a specific variant, for example a particular size or color, the displayed price changes dynamically from the range to a fixed value. This change must be announced via an aria-live="polite" region, since otherwise the price gets updated silently in an already rendered, not newly focused element, and screen reader users simply miss the new, binding figure.


<p id="product-price" aria-live="polite">
  from 19.99 EUR
</p>

<script>
function updatePriceForVariant(price) {
  document.getElementById('product-price').textContent = price + ' EUR';
}
</script>

7. Color coding for discounts and contrast requirements

A reduced price is frequently also shown in red visually to set it apart from regular prices. Under WCAG success criterion 1.4.1, color must never be the only distinguishing feature, which is why the strikethrough line on the old price, the changed font weight on the new price, or an additional badge must always be preserved as a non color signal, even when red is additionally used.

The contrast of the red price text against the background must meet the same WCAG minimum as any other text, at least 4.5 to 1 for normal body text. A common mistake is a too bright, eye catching red that looks striking but fails the required contrast ratio on a light background, making it hard to read for users with visual impairments.

8. Currency and number format readability for screen readers

Large price figures with thousands separators, for example "1,299.00 EUR", get read out differently by some screen readers depending on language and locale settings, in rare cases even as a sequence of individual digits instead of one coherent number. A correctly set lang attribute on the HTML document or the respective price element makes sure the screen reader's speech engine applies the right pronunciation rule for number formats.

Currency symbols like "EUR" or "€" should be used consistently, either always as an abbreviation or always as a symbol, since some screen readers correctly pronounce the euro symbol as "euro" while others skip it silently or announce it as an unknown character. A textual abbreviation like "EUR" is in practice the more reliable choice for consistent announcements across different assistive technologies.

9. Implementation in Magento and Hyvä: adapting the price block template

In a Hyvä theme, the del/ins pattern can be added directly to the price render template of the product listing and product detail page, so the original Magento price block still serves as the central data source but gets output with correct semantic markup and an additional sr-only context sentence. The dynamic price update on variant selection runs through the same Alpine.js component that also controls the variant selector.

The inline script block for the aria-live price update must be registered via $hyvaCsp->registerInlineScript() in the corresponding phtml template, so the Content Security Policy does not block execution.


document.addEventListener('alpine:init', () => {
  Alpine.data('variantPrice', () => ({
    priceLabel: 'from 19.99 EUR',
    updatePrice(newPrice) {
      this.priceLabel = newPrice + ' EUR';
    },
  }));
});
Price element Technique Purpose Common mistake
Old price del element instead of text-decoration Marking semantically as invalid Plain CSS strikethrough on a span
New price ins element Marking semantically as the current value No semantic element, only bold text
Full price comparison sr-only context sentence Complete statement regardless of screen reader Only two isolated numbers with no connection
Discount badge aria-label with the percentage spelled out Clarifying what the figure refers to A plain number with no context in the badge
Price range for variants aria-live region on change Announcing the new price after selection Silent update with no live region

Mironsoft

WCAG audits, accessible Magento shops, and training

Not sure whether the shop is actually accessible?

We audit existing Magento shops against WCAG 2.2, fix concrete barriers in the Hyvä frontend, and train teams so accessibility stays anchored in the development process for good.

WCAG Audit

Systematically review the shop against WCAG 2.2 AA, with a prioritized issue list.

Fixing Barriers

Concrete implementation: keyboard operability, screen reader support, contrast, forms.

Team Training

Raise developer and editor awareness for accessible implementation day to day.

10. Summary

Accessible Price Display: The Essentials

Core idea

del and ins replace plain CSS strikethrough and represent the relationship between old and new price semantically correctly.

Redundant safeguard

An sr-only context sentence with the full price comparison secures the announcement regardless of how a screen reader handles del/ins.

Discount badges

An aria-label with the percentage spelled out clarifies the meaning of an isolated figure inside the badge.

Price ranges

A live region announces the final price after variant selection, since otherwise the value changes silently in the DOM.

11. FAQ: Accessible Price Display: The Essentials

1Why is text-decoration line-through not enough for an old price?
It is pure style information with no semantic meaning and changes neither the role nor the announcement of the element in the accessibility tree.
2Which HTML elements are correct for old and new prices?
del for the original, now invalid price and ins for the current, reduced price, which represents the relationship semantically correctly.
3Do all screen readers automatically announce del and ins audibly?
Not reliably in every combination. An additional sr-only context sentence with the full price comparison secures the announcement further.
4What should an sr-only context sentence about the price contain?
The complete comparison as a natural sentence, for example Instead of 79.99 euros, now 59.99 euros, you save 25 percent, rather than just two isolated numbers.
5Does a discount badge with only a percentage figure need an aria-label?
Yes, as long as the badge is the sole source of the information, otherwise it stays unclear what the figure refers to.
6When should a discount badge be hidden with aria-hidden?
When the discount information already exists in full as text elsewhere, to avoid duplicate announcements.
7How should a price range for product variants be phrased?
With the word from or an equivalent phrasing, so it is clear that this is not a fixed price.
8Does the price change after variant selection need to be actively announced?
Yes, via an aria-live region, since a silent DOM update with no focus change would otherwise be missed by screen reader users.
9Is red text alone enough to mark a reduced price?
No, under WCAG 1.4.1 color must not be the only distinguishing feature, an additional non color signal is mandatory.
10Should I write currency as a symbol or as an abbreviation like EUR?
A textual abbreviation like EUR is more reliable in practice, since some screen readers pronounce the euro symbol inconsistently or not at all.