What is the CSS Calculator?
This is a calculator built entirely with HTML and CSS, without using any JavaScript.
Normally, “computational processing” and “state management” for calculators require scripting languages like JavaScript, but this project implements the four basic arithmetic operations using only CSS.
For those wondering, “What is CSS?”, the explanation page is.....
Sorry, there isn't one.
(Since this site focuses on the “limits of CSS,” we have omitted basic explanations)
Can it really perform calculations?
First, Here is the actual calculator.
Please try performing a calculation yourself.
Were you able to perform arithmetic operations on three-digit numbers?
Example) 308 × 912 = 280896
*Behavior may vary by browser, but Chromium-based browsers have the fewest bugs.
Objective
I wanted to understand the limits of CSS
I took on the challenge of “UI/UX design under constraints” and “managing button states using CSS.”
Overview
I got the idea while digging through CSS specifications.
I kept up with the latest CSS features, such as :has(), and refactored the code repeatedly until it reached its current form.
I struggled with browser-specific behavior differences and rounding errors in `calc()` for decimal calculations, so I adjusted the combination of CSS variables many times.
How was it made?
I’ll describe the detailed trial-and-error process later, but here’s a brief summary.
Skip this section unless you’re in a hurry
(It’s still a work in progress...)
Mechanism & Code Explanation
Without further ado, here’s the code.
<form>
.
.
.
...I want to display this
.
Note : There wasn't enough space to write all the code...
*The code is available here.
From here on, I’ll explain the code by breaking it down into four parts.
a. Input
b. Operations
Determining input state using the “:checked” pseudo-class
Used in conjunction with the “:has()” pseudo-class
Specify CSS variables for :root based on the input state
/* Set the hundreds digit to 3 */
:has([data-x0=“3”]:checked) {
--x0: 3;
}
Perform calculations using calc()
The following CSS sets a three-digit number to the CSS variable X.
/* Multiply the hundreds digit by 100, multiply the tens digit by 10, and add them together */
:root{
--X: calc(var(--x0)*100 + var(--x1)*10 + var(--x2))
}
c. Output
There is an area at the top of the calculator to display the calculation results.
I also struggled with the process of converting the results of CSS variable calculations into text.
The approaches range from primitive methods that specify the display for each calculation result, to those using CSS animations, and those using CSS counters.
The following is the minimal configuration for output using CSS counters.
/* Define a CSS counter for the calculation result area */
#result{
counter-reset: n calc(var(--X) * var(--Y));
}
/* Display the CSS counter value */
#result .digit-x01::before{
content: counter(n);
}
Set the calculation result in `counter-reset` using `calc()`.
For multi-digit numbers, the `var(--x)` part is also set using a complex `calc()` expression.
Please also take a look at how the division by zero counter is displayed.
d.UI
We paid meticulous attention not only to calculation accuracy but also to the UI.
This calculator uses over a hundred radio buttons, but
we change the button display state based on the input status
or disabled.
*To simplify the code, we transformed the radio buttons into regular buttons without using wrappers.
Implementation of special buttons such as the reset button
—please also take note of the evolution of the design based on grid layouts.
History of the Calculator
The First Calculator
It can only perform multiplication.
Set the calculation result to the “element width” and reference it with @container
The width of the dummy element is specified using the calc() function in a CSS variable, and that width is retrieved via @container to display the calculation result based on conditional branching.
Whether it’s 3×4 or 6×2, it reaches the same conditional branch.
The reset button was implemented from the very first version.
<input type="reset">
Since this was the author’s first-ever CSS calculator, it was groundbreaking at the time.
However, the biggest drawback of this calculator is that the amount of code increases proportionally to the number of calculation results.
Second-generation calculator
Displaying results using CSS animations
The `content` property of the calculation result's pseudo-element (::before) is toggled using a CSS animation, and its `animation-iteration-count` is set via a CSS variable.
/* For division. Code to display the ones digit */
#result::before{
animation-name: result;
animation-duration: 5ms;
animation-fill-mode: both;
animation-timing-function: step-end;
animation-iteration-count: calc(var(--X) / var(--Y) / 10);
}
@keyframes result{
0%{ content: “0”; }
10%{ content: “1”; }
20%{ content: “2”; }
30%{ content: “3”; }
40%{ content: “4”; }
50%{ content: “5”; }
60%{ content: “6”; }
70%{ content: “7”; }
80%{ content: “8”; }
90%{ content: “9”; }
100% {content: “0”; }
}
Animation? What does that mean? If you're wondering, try performing a division by zero.
*When performing a division by zero operation, in many browsers the value of `animation-iteration-count` diverges to infinity, causing the animation to continue indefinitely.
Since `animation-iteration-count` can take on decimal values, for example, if you substitute 6 for X and 3 for Y, the value “0.2” is set, and the animation stops with `content` set to “2”.
...In this way, I’ve elegantly solved the problem of code volume increasing in proportion to the calculation pattern...
Does not support results less than 1
Rounding errors occur in browsers other than Chromium
I want to handle cases where the result is a decimal or a negative number!
Calculations involving just single-digit numbers aren’t enough!
3rd-generation calculator
Now supports multiple digits
Now supports negative values
Corrects behavior differences across browsers
Major UI changes
We have switched to displaying results using CSS counters.
*Compared to the CSS animation method, this virtually eliminates errors in Safari. We have also minimized errors in Firefox.
Added support for cases where the calculation result is a negative number.
Improved the UI and made more elements disabled.
We have adopted the animation-based output from the second-generation calculator only for division operations where the result can be a decimal number.
The division results look odd.
For example, when calculating “3÷9,” it outputs “00000000.333333,” resulting in a leading zero.
Furthermore, it has limited extensibility.
Implementing features such as sign reversal, floating-point operations, and single-character deletion required a complete rewrite of the code.
To the 4th-generation calculator
The UI was completely redesigned to look more like a calculator.
For numbers up to 46,000, there is no error in any browser.
It is now possible to change the sign.
For division, we now output only the integer part; by using CSS counters, we’ve resolved the issue of leading zeros.
We’ve added “...” when the division isn’t exact.
I’d like a feature to delete just one character (⌫)
I’d like to enable calculations between decimals
I want to put the whole thing in a namespace to make it easier to handle
* Since load speed for selectors prefixed with “:has()” depends on page size, I want to resolve this by wrapping them in @scope.
Bonus: Minimalist calculator
<div class="calc-wrap">
<div style="--n:1">
<input type="checkbox">1
</div>
<div style="--n:2">
<input type="checkbox">2
</div>
<div style="--n:3">
<input type="checkbox">3
</div>
<div style="--n:4">
<input type="checkbox">4
</div>
<div style="--n:5">
<input type="checkbox">5
</div>
<div style="--n:6">
<input type="checkbox">6
</div>
<div style="--n:7">
<input type="checkbox">7
</div>
<div style="--n:8">
<input type="checkbox">8
</div>
<div class="result">Total:</div>
</div>
.calc-wrap :has(:checked) {
counter-increment: n var(--n);
}
.calc-wrap .result::after {
content: counter(n);
}
The code is free. Feel free to use it as a reference.
Exclusive release of the latest calculator!
It can perform calculations between decimals.
We’ve also implemented a “⌫” function to delete a single character!
We’ve wrapped the CSS in @scope to make it easier to handle.
*For calculations where the result is between -1 and 0 (e.g., 5 ÷ (-8)), incorrect results may be output. This is especially true when changing the sign (+/-).
*The upper limit for calculation results is ±2147683647.
Well, there’s one button left over (the “?” button, for future expansion).
If I find a good use for it, I’ll implement it in the future, so please stay tuned!!
Related Works
There are other projects that test the limits of CSS!
Diagnostic sites, random number generators, SPAs, tic-tac-toe, and more...
Tic-Tac-Toe
SPA