Calculating Discounted Prices with Pure CSS: A Modern Approach
Introduction
CSS has evolved far beyond simply styling elements. With modern mathematical functions and powerful selectors, you can now perform real-time calculations directly in the stylesheet — no JavaScript required. One practical application is calculating and displaying discounted prices on e-commerce or subscription pages. This article walks you through a pure CSS solution that computes the sale price based on a base price and discount percentage, using only HTML and CSS.

Why Use CSS for Price Calculations?
Traditionally, dynamic price updates rely on JavaScript to fetch values and manipulate the DOM. But CSS can handle arithmetic operations via the calc() function, read custom data attributes with attr(), and conditionally apply styles using the :has() selector. This approach reduces client-side scripting, eliminates latency, and conserves browser resources. While some features are still gaining browser support, this technique showcases the direction of modern CSS and is a valuable exercise for future-ready web development.
Example Overview: Streaming Service Subscription
Imagine a pricing interface for streaming services — Netflix, Disney+, HBO, etc. Each service has a base monthly price and a student discount (e.g., 20% off). A checkbox toggles the discount, and the CSS automatically strikes through the original price and reveals the discounted price. Everything is driven by data-* attributes and CSS math.
The Markup
Each subscription item is a list element containing a label with the service name, a price element storing the base price and discount as data-price and data-discount, and checkboxes for selection and discount toggle.
<li class="ott">
<label>
<span>Netflix</span>
<div class="ott-price" data-price="7.99" data-discount="0.2">$7.99</div>
<input type="checkbox" class="is-ott-selected">
</label>
<label>
<span>Apply Student Discount (20%)</span>
<input type="checkbox" class="is-ott-discounted">
</label>
</li>
The data-price holds the original numeric price, and data-discount stores the discount as a decimal (e.g., 0.2 for 20% off). The original text inside the element is for fallback display before any calculation.
Striking the Original Price
When the student discount checkbox is checked, the original price should be crossed out. The CSS uses the :has() selector to detect the checked state and apply text-decoration: line-through to the .ott-price element.
.ott:has(.is-ott-discounted:checked) {
.ott-price {
text-decoration: line-through;
}
}
This works because :has() allows styling a parent based on the state of a descendant. In this case, the parent .ott is targeted when it contains a checked discount toggle.
Calculating the Discounted Price
Now we need to display the new price next to (or after) the struck-through original. We can use a pseudo-element such as ::after to show the calculated value. The CSS calc() function performs the arithmetic, while attr() retrieves the numeric values from the data-* attributes.

.ott:has(.is-ott-discounted:checked) {
.ott-price::after {
content: "$" calc(attr(data-price number) * (1 - attr(data-discount number)));
margin-left: 0.5em;
color: green;
font-weight: bold;
}
}
Note: The attr() function with a type (e.g., number) is part of the CSS Values and Units Module Level 5, which is still in draft and lacks widespread browser support. However, when fully supported, this will enable seamless numeric extraction. For now, you might need a fallback or a JavaScript polyfill, but the concept remains valid.
Styling the Output
Once the discounted price appears, you can enhance it with visual cues. For example, add a green color to denote savings, or a larger font size to emphasize the deal. You can also conditionally show the discount percentage as a badge using the same attr() approach.
.ott:has(.is-ott-discounted:checked) {
.ott-price::before {
content: attr(data-discount) "% off ";
color: #c00;
}
}
Limitations and Browser Support
The technique relies on cutting-edge CSS features. As of now, attr() with units or numbers is not supported in any major browser. The :has() selector is available in recent versions of Chrome and Safari but not yet in Firefox (without a flag). Therefore, for production use, you would likely fall back to JavaScript or use this approach as progressive enhancement. Nevertheless, experimenting with these features keeps you ahead of the curve and prepares you for when they become universal.
Conclusion
CSS today is a powerful tool capable of more than just styling — it can perform real-world calculations and conditional displays. By combining :has(), calc(), and attr(), you can create a self-contained price discount system without a single line of JavaScript. While browser support is still growing, this pattern illustrates the potential of CSS as a fully‑fledged logic layer. Start experimenting now to be ready for tomorrow's web standards.
Internal Links
Related Articles
- 5 Key Mechanisms React Uses to Efficiently Detect UI Changes
- New Open-Source Framework Plasmo Dramatically Simplifies Chrome Extension Development
- Optimizing Pull Request Performance at GitHub: A Q&A on the Files Changed Tab
- 6 Game-Changing Updates in Copilot Studio’s .NET 10 WebAssembly Upgrade
- 5 Ways V8 Made JSON.stringify Twice as Fast (And What It Means for Your Code)
- 4 Revolutionary Web Development Techniques You Need to Know: From Canvas HTML to E-Ink OS
- 5 Performance Breakthroughs That Transformed GitHub's Pull Request Diffs
- In-Browser Testing for Vue Components: A Node-Free Approach