In short
A calculator teaches you more than maths. You'll handle a grid of button clicks, remember what's been typed, and fight edge cases like 0.1 + 0.2 and dividing by zero.
A working calculator UI with button clicks driving arithmetic logic — deceptively tricky once you handle decimals, chained operations and edge cases correctly. A calculator exposes state-management challenges such as input parsing, operator precedence, repeated operations, decimal handling, and recovery from invalid sequences.

Think of it like this
The shopkeeper's calculator
The shopkeeper punches 250, plus, 120, equals and reads out the total. Behind that tiny screen, the calculator is remembering the first number, the operator and the second number. Building that memory is your real job.
Watch it happen
Working out 12 + 30
1, 2
1/7Digit clicks build the current number as a string: "12".
The big ideas
What you need to know
Three pieces of state
The previous number, the current number being typed, and the chosen operator.
One listener for the grid
Put data-value on each button and handle every click from the parent with event delegation.
Keyboard support
Listen for keydown so 0 to 9, + - * /, Enter and Backspace work too.
No eval
eval runs any code it's given, which is risky and hard to control. Write a small compute function instead.
How it works
Build plan
- 1
Layout
A display and a 4-column button grid with CSS Grid.
- 2
Digits
Clicking digits appends to the current number on screen.
- 3
Operators
Store the current number and the operator, then start a fresh number.
- 4
Equals
Compute, show the result, and let it become the next starting number.
- 5
Edge cases
Clear, one decimal point only, divide by zero, and keyboard input.
See it in code
Walk through the code
1const state = { prev: null, current: "0", op: null };2 3function compute(a, b, op) {4 const x = Number(a), y = Number(b);5 switch (op) {6 case "+": return x + y;7 case "-": return x - y;8 case "*": return x * y;9 case "/": return y === 0 ? "Error" : x / y;10 }11}12 13const result = compute("0.1", "0.2", "+");14console.log(result); // 0.3000000000000000415console.log(Number(result.toFixed(10))); // 0.31/4Three pieces of state: previous number, current number and operator.
Display values are strings, so convert with Number before maths. Rounding with toFixed and then Number hides floating point noise in the display.
Save yourself the pain
Common mistakes
Oops
Using eval(displayText) to get the answer.
Do this instead
It runs any code in that string. A small compute function with a switch is safer and easier to debug.
Oops
Letting users type 1.2.3.
Do this instead
Before adding a dot, check if the current number already includes(".").
Oops
Adding display strings directly, so "2" + "3" shows 23.
Do this instead
Convert with Number() before you calculate.
Your mission
Milestone 1: add two numbers
Quick check
Your display values are strings. What does "7" + "3" give?
Go deeper
- 1Handle operator chaining correctly (e.g. `5 + 3 * 2` in left-to-right or precedence order). Validate your understanding by applying it in a small practical example.
- 2Add keyboard support in addition to button clicks. Test the result on both a successful path and an invalid or unexpected input.
- 3Test edge cases: dividing by zero, multiple decimal points, clearing state.
More projects at this stop
Stuck on this topic? Ask an Insider
Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.