We're used to reaching for JavaScript whenever we need to perform calculations on the front end. But modern CSS is getting surprisingly powerful. With the evolution of the attr() function and the addition of math functions like calc(), mod(), and round(), we can now handle complex tasks directly in our stylesheets. Let's explore how to calculate and display a discounted price for an e-commerce interface, all without a script.

Developer inspecting CSS code in browser devtools to calculate discounted prices Dev Environment Setup

The Initial Markup: Setting the Stage

We'll build a simple subscription pricing panel for a streaming service. The HTML structure uses data-* attributes to store the base price and discount percentage. This keeps the presentation layer clean and makes the data easily accessible for our CSS logic.

<div class="ott">
  <input type="checkbox" id="discount" class="is-ott-discounted">
  <label for="discount">Apply Student Discount</label>

  <div class="ott-card">
    <h3>Netflix</h3>
    <p class="ott-price" data-price="7.99" data-discount="0.20">$7.99</p>
  </div>
</div>

Calculating the Discount: The Power of attr() and calc()

When the user clicks the discount toggle, we can use the :has() selector to detect the change and apply our calculations. The key is the updated attr() function, which can now parse values into specific types like number.

/* When the discount toggle is checked inside the .ott container */
.ott:has(.is-ott-discounted:checked) {
  /* Strike through the original price */
  .ott-price {
    text-decoration: line-through;

    /*
      Calculate the new price from the data-* attributes:
      Original Price * (1 - Discount Applied)
    */
    --n: calc(attr(data-price number) * (1 - attr(data-discount number)));
  }
}

In this snippet, attr(data-price number) extracts the value 7.99 as a number, and attr(data-discount number) extracts 0.20. The calc() function then computes the discounted price. The result is stored in a custom property --n.

Displaying the Result: Splitting Numbers with mod() and round()

CSS counters can't handle decimals natively. To display the price accurately (e.g., $6.39), we need to separate the whole number and the decimal part. This is where the mod() and round() functions shine.

.ott:has(.is-ott-discounted:checked) {
  .ott-price {
    /* ... previous styles ... */
    &::after {
      display: inline-block;

      /*
        Splits the variable --n into two counters:
        'a' for the whole number (in dollars) and 'b' for the decimals (in cents)
      */
      counter-set: a calc(round(down, var(--n))) b calc((mod(var(--n), 1)) * 100);

      /* Output: two spaces (\2000), a dollar sign ($), the number, a dot, and the decimals */
      content: "\2000\2000
quot; counter(a) "." counter(b, decimal-leading-zero); } } }

Here, round(down, var(--n)) gives us the integer part (e.g., 6), and mod(var(--n), 1) isolates the fractional part, which we then multiply by 100 to get the cents (e.g., 39). The content property assembles the final string with the dollar sign and decimal point.

E-commerce product card layout displaying original and discounted prices computed with CSS Coding Session Visual

Limitations and Caveats

While this is a fantastic demonstration of modern CSS power, it's important to be aware of its current limitations.

  • Browser Support: The upgraded attr() function with type parsing is still not widely supported (it's not Baseline). The mod() and round() functions are more recent additions. Always check caniuse.com for the latest support data.
  • Accessibility: Screen readers may not interpret the content property's generated text in the same way as actual HTML text. Use this technique where the visual result is primary and ensure the original price is still in the DOM for context.
  • Complexity: For complex pricing logic (e.g., multi-tiered discounts, tax calculations), JavaScript remains a more maintainable and scalable solution. This CSS approach is best for simple, static calculations.

Next Steps for Learning

To build on what you've learned here, consider exploring these areas:

  1. CSS Typed OM: The CSS Typed Object Model is the API that makes this new attr() behavior possible. Learning about it will deepen your understanding of how CSS values work.
  2. Container Queries: Combine these math functions with container queries to create truly responsive components that adapt their pricing display based on their container's size.
  3. Experimentation: Try building other UI elements, like a progress bar that uses mod() to create a striped pattern or a dynamic rating system that uses round().

Laptop showing a streaming service subscription page with student discount applied via CSS System Abstract Visual

Wrapping Up

This technique showcases the exciting evolution of CSS from a styling language to a full-fledged logic layer. While we used it to calculate a discounted price, the same principles can be applied to other UI tasks, like showing completion percentages or creating data-driven visualizations. The key takeaway is that the line between 'style' and 'function' is blurring, and CSS is becoming an increasingly capable tool for the modern developer. For more insights into how large-scale systems handle complex logic, check out this piece on how Airbnb broke circular dependencies in observability.

This content was drafted using AI tools based on reliable sources, and has been reviewed by our editorial team before publication. It is not intended to replace professional advice.