What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

You can build a working four-operation calculator with three files: HTML for the interface, CSS for the layout, and JavaScript for input, state, arithmetic, and error handling. This tutorial creates a calculator with decimal numbers, addition, subtraction, multiplication, division, Clear, Delete, accessible buttons, and optional keyboard support—without using eval().

What you will build

  • Number buttons from 0 to 9
  • Decimal input
  • Addition, subtraction, multiplication, and division
  • Equals, Clear, and Delete controls
  • Division-by-zero and invalid-result handling
  • Optional keyboard controls

The finished calculator evaluates one operation at a time, like a basic pocket calculator. It is not a full expression parser, so an input such as 2 + 3 × 4 is processed sequentially rather than with normal mathematical precedence.

Prerequisites

You need a modern browser, a text editor, and basic HTML knowledge. No framework, package manager, build tool, or dependency is required. You can use a local editor such as Visual Studio Code, or experiment immediately in CodePen.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Create the project files

calculator/
├── index.html
├── styles.css
└── script.js

HTML creates the structure, CSS controls its appearance, and JavaScript supplies behavior through the browser DOM. Keeping the files separate makes the relationship between these technologies easier to understand. JavaScript can also be embedded with a <script> element, but an external file is preferable once the example grows. See MDN’s guide to adding JavaScript to a web page.

#1 Best Overall
15.6 Inch Laptops, Core i3 Processor, 8GB RAM, 128GB SSD, Backlit Keyboard
  • RELIABLE PROCESSOR: Adopts Core i3 processor with dual core quad thread design and 2.0 GHz base frequency delivers steady running performance to support smooth daily office web browsing and multitasking operation
  • 15.6 INCH HD SCREEN & ULTRA PORTABLE BODY: Features 15.6 inch high definition screen for clear daily viewing experience comes with lightweight 1.6 kg body easy to carry around for commuting business trips and outdoor study anytime
  • FULL SIZE KEYBOARD: Built in full size keyboard with independent numeric keypad equipped with backlight design for dark environment typing supports fingerprint unlock for private data protection and matches a large sensitive touchpad for smooth control
  • COMPLETE RICH EXPANSION PORTS: Built in sufficient side interfaces, including 2 USB 3.0 ports, 3.5mm audio jack, HDMI interface, MicroSD (TF) card slot, and DC power port to meet all daily needs
  • STABLE WIRELESS CONNECTION: Built in Bluetooth 4.2 for quick pairing with wireless peripherals supports 5G WiFi network to realize faster transmission speed and more stable network signal for daily online work and entertainment

1. Build the HTML interface

Create index.html:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Simple Calculator</title>
  <link rel="stylesheet" href="styles.css">
  <script src="script.js" defer></script>
</head>
<body>
  <main class="calculator" aria-labelledby="calculator-title">
    <h1 id="calculator-title">Simple Calculator</h1>

    <output id="display" class="display" aria-live="polite" aria-label="Calculator result">0</output>

    <div class="keys" id="calculator-keys">
      <button type="button" data-action="clear" class="function-key">Clear</button>
      <button type="button" data-action="delete" class="function-key">Delete</button>
      <button type="button" data-operator="/" class="operator-key" aria-label="Divide">÷</button>
      <button type="button" data-operator="*" class="operator-key" aria-label="Multiply">×</button>

      <button type="button" data-number="7">7</button>
      <button type="button" data-number="8">8</button>
      <button type="button" data-number="9">9</button>
      <button type="button" data-operator="-" class="operator-key" aria-label="Subtract">−</button>

      <button type="button" data-number="4">4</button>
      <button type="button" data-number="5">5</button>
      <button type="button" data-number="6">6</button>
      <button type="button" data-operator="+" class="operator-key" aria-label="Add">+</button>

      <button type="button" data-number="1">1</button>
      <button type="button" data-number="2">2</button>
      <button type="button" data-number="3">3</button>
      <button type="button" data-action="equals" class="equals-key">=</button>

      <button type="button" data-number="0" class="zero-key">0</button>
      <button type="button" data-action="decimal">.</button>
    </div>
  </main>
</body>
</html>

Each control is a real <button>, which provides built-in keyboard and touch behavior. The data-number, data-operator, and data-action attributes give JavaScript a clear description of each button’s purpose. Symbol-only controls have accessible labels.

2. Add basic styling

Create styles.css. CSS is not needed for arithmetic, but it makes the controls usable and recognizable as a calculator.

:root {
  font-family: system-ui, sans-serif;
  color-scheme: light dark;
}

* { box-sizing: border-box; }

body {
  min-height: 100vh;
  margin: 0;
  display: grid;
  place-items: center;
  background: #eef2f7;
}

.calculator {
  width: min(92vw, 360px);
  padding: 1rem;
  border-radius: 1rem;
  background: #1f2937;
  box-shadow: 0 1rem 2rem rgb(0 0 0 / 20%);
}

h1 {
  margin: 0 0 1rem;
  color: white;
  font-size: 1.25rem;
  text-align: center;
}

.display {
  display: block;
  width: 100%;
  min-height: 4rem;
  margin-bottom: 1rem;
  padding: .75rem;
  overflow-x: auto;
  border-radius: .5rem;
  background: #111827;
  color: white;
  font-size: 2rem;
  line-height: 1.5;
  text-align: right;
  white-space: nowrap;
}

.keys {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: .5rem;
}

button {
  min-height: 3.25rem;
  border: 0;
  border-radius: .5rem;
  background: #e5e7eb;
  color: #111827;
  cursor: pointer;
  font: inherit;
  font-size: 1.25rem;
}

button:hover { background: #d1d5db; }
button:focus-visible { outline: 3px solid #93c5fd; outline-offset: 2px; }
.operator-key { background: #f59e0b; }
.function-key { background: #9ca3af; }
.equals-key { grid-row: span 2; background: #22c55e; }
.zero-key { grid-column: span 2; }

3. Represent the calculator’s state

The calculator needs to remember what is displayed, the first number in a pending operation, the selected operator, and whether the next digit should replace the display. Add this beginning to script.js:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const display = document.querySelector("#display");
const keys = document.querySelector("#calculator-keys");

let currentValue = "0";
let storedValue = null;
let operator = null;
let waitingForOperand = false;
  • currentValue is the number currently shown. It remains a string while the user is typing, so values such as 0. can be represented.
  • storedValue is the first number in an operation.
  • operator stores +, -, *, or /.
  • waitingForOperand tells the calculator that the next digit begins a new number.

4. Handle display updates, numbers, and decimals

function updateDisplay() {
  display.textContent = currentValue;
}

function formatResult(value) {
  if (!Number.isFinite(value)) {
    return "Error";
  }

  // Hide common floating-point display artifacts.
  return String(Number(value.toFixed(10)));
}

function inputNumber(number) {
  if (currentValue === "Error" || waitingForOperand) {
    currentValue = number;
    waitingForOperand = false;
  } else if (currentValue === "0") {
    currentValue = number;
  } else {
    currentValue += number;
  }

  updateDisplay();
}

function inputDecimal() {
  if (currentValue === "Error" || waitingForOperand) {
    currentValue = "0.";
    waitingForOperand = false;
  } else if (!currentValue.includes(".")) {
    currentValue += ".";
  }

  updateDisplay();
}

Button text is a string, while arithmetic needs numbers. Convert with Number() only when calculating. This avoids prematurely changing an in-progress value such as 0.. The decimal check prevents malformed input such as 1.2.3, and the replacement rule prevents sequences such as 0007. JavaScript number conversion and arithmetic operators are described in MDN’s math guide.

5. Perform the arithmetic explicitly

function calculate(first, second, selectedOperator) {
  switch (selectedOperator) {
    case "+":
      return first + second;
    case "-":
      return first - second;
    case "*":
      return first * second;
    case "/":
      if (second === 0) {
        throw new Error("Cannot divide by zero");
      }
      return first / second;
    default:
      return second;
  }
}

The function converts no strings itself; its callers pass numeric values. Division by zero is checked before division, so the interface can show an error instead of displaying Infinity.

6. Connect operators and Equals

function handleOperator(nextOperator) {
  const inputValue = Number(currentValue);

  if (!Number.isFinite(inputValue)) {
    resetCalculator();
    return;
  }

  if (operator && storedValue !== null && !waitingForOperand) {
    try {
      const result = calculate(Number(storedValue), inputValue, operator);
      currentValue = formatResult(result);
      storedValue = Number(currentValue);
      updateDisplay();
    } catch {
      showError();
      return;
    }
  } else {
    storedValue = inputValue;
  }

  operator = nextOperator;
  waitingForOperand = true;
}

function handleEquals() {
  if (operator === null || storedValue === null) {
    return;
  }

  const first = Number(storedValue);
  const second = Number(currentValue);

  try {
    const result = calculate(first, second, operator);
    currentValue = formatResult(result);
    storedValue = null;
    operator = null;
    waitingForOperand = true;
    updateDisplay();
  } catch {
    showError();
  }
}

When the user selects an operator, the displayed value becomes the stored first number. If another operator is selected after a second number has been entered, the pending operation is completed first. Equals then converts both values, calculates, formats, displays, and retains the result as the current value.

7. Add Clear, Delete, and error recovery

function deleteLastCharacter() {
  if (currentValue === "Error" || waitingForOperand) {
    return;
  }

  currentValue = currentValue.slice(0, -1);

  if (currentValue === "" || currentValue === "-") {
    currentValue = "0";
  }

  updateDisplay();
}

function resetCalculator() {
  currentValue = "0";
  storedValue = null;
  operator = null;
  waitingForOperand = false;
  updateDisplay();
}

function showError() {
  currentValue = "Error";
  storedValue = null;
  operator = null;
  waitingForOperand = true;
  updateDisplay();
}

Delete never leaves an empty display. After an error, entering a number starts over; Clear also resets every state variable. Pressing Equals without a stored value or operator simply does nothing.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

8. Register one event listener

Finish script.js with event delegation:

keys.addEventListener("click", (event) => {
  const button = event.target.closest("button");

  if (!button) {
    return;
  }

  if (button.dataset.number !== undefined) {
    inputNumber(button.dataset.number);
    return;
  }

  if (button.dataset.operator !== undefined) {
    handleOperator(button.dataset.operator);
    return;
  }

  switch (button.dataset.action) {
    case "decimal":
      inputDecimal();
      break;
    case "equals":
      handleEquals();
      break;
    case "clear":
      resetCalculator();
      break;
    case "delete":
      deleteLastCharacter();
      break;
  }
});

Rather than adding a separate inline onclick attribute to every button, this attaches one listener to the button container and identifies the clicked control. MDN recommends addEventListener() for event handling and notes the limitations of inline handlers: MDN events guide.

Complete JavaScript file

For convenience, here is the complete script.js file:

const display = document.querySelector("#display");
const keys = document.querySelector("#calculator-keys");

let currentValue = "0";
let storedValue = null;
let operator = null;
let waitingForOperand = false;

function updateDisplay() { display.textContent = currentValue; }

function formatResult(value) {
  if (!Number.isFinite(value)) return "Error";
  return String(Number(value.toFixed(10)));
}

function calculate(first, second, selectedOperator) {
  switch (selectedOperator) {
    case "+": return first + second;
    case "-": return first - second;
    case "*": return first * second;
    case "/":
      if (second === 0) throw new Error("Cannot divide by zero");
      return first / second;
    default: return second;
  }
}

function inputNumber(number) {
  if (currentValue === "Error" || waitingForOperand) {
    currentValue = number;
    waitingForOperand = false;
  } else if (currentValue === "0") {
    currentValue = number;
  } else {
    currentValue += number;
  }
  updateDisplay();
}

function inputDecimal() {
  if (currentValue === "Error" || waitingForOperand) {
    currentValue = "0.";
    waitingForOperand = false;
  } else if (!currentValue.includes(".")) {
    currentValue += ".";
  }
  updateDisplay();
}

function handleOperator(nextOperator) {
  const inputValue = Number(currentValue);
  if (!Number.isFinite(inputValue)) { resetCalculator(); return; }

  if (operator && storedValue !== null && !waitingForOperand) {
    try {
      const result = calculate(Number(storedValue), inputValue, operator);
      currentValue = formatResult(result);
      storedValue = Number(currentValue);
      updateDisplay();
    } catch { showError(); return; }
  } else {
    storedValue = inputValue;
  }
  operator = nextOperator;
  waitingForOperand = true;
}

function handleEquals() {
  if (operator === null || storedValue === null) return;
  try {
    const result = calculate(Number(storedValue), Number(currentValue), operator);
    currentValue = formatResult(result);
    storedValue = null;
    operator = null;
    waitingForOperand = true;
    updateDisplay();
  } catch { showError(); }
}

function deleteLastCharacter() {
  if (currentValue === "Error" || waitingForOperand) return;
  currentValue = currentValue.slice(0, -1);
  if (currentValue === "" || currentValue === "-") currentValue = "0";
  updateDisplay();
}

function resetCalculator() {
  currentValue = "0";
  storedValue = null;
  operator = null;
  waitingForOperand = false;
  updateDisplay();
}

function showError() {
  currentValue = "Error";
  storedValue = null;
  operator = null;
  waitingForOperand = true;
  updateDisplay();
}

keys.addEventListener("click", (event) => {
  const button = event.target.closest("button");
  if (!button) return;
  if (button.dataset.number !== undefined) return inputNumber(button.dataset.number);
  if (button.dataset.operator !== undefined) return handleOperator(button.dataset.operator);

  switch (button.dataset.action) {
    case "decimal": inputDecimal(); break;
    case "equals": handleEquals(); break;
    case "clear": resetCalculator(); break;
    case "delete": deleteLastCharacter(); break;
  }
});

Test the calculator

Save all three files and open index.html in your browser. The defer attribute ensures the external script runs after the HTML has been parsed. If you prefer, place the script immediately before </body>.

Test Expected result
2 + 3 = 5
8 - 10 = -2
4 × 6 = 24
9 ÷ 3 = 3
5 ÷ 0 = Error
0.1 + 0.2 = Rounded display result
Second decimal point Ignored
Clear 0
Delete Removes the final character
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Why this example does not use eval()

A short calculator tutorial often builds a string such as 12+7*3 and passes it to eval(). That is not recommended here. eval() executes JavaScript represented as a string, not merely arithmetic. If untrusted text reaches it, an attacker may be able to execute arbitrary code. It can also be blocked by common Content Security Policy configurations. See MDN’s eval() reference.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The explicit switch is longer, but it limits the calculator to known operations and makes the behavior easier to inspect and extend. It avoids the arbitrary-code-execution risk associated with sending input to eval(); that is not the same as claiming the entire application has undergone a security audit.

If you need parentheses, unary operators, scientific functions, or normal precedence, use a tokenizer and parser—or a well-maintained, security-reviewed math-expression library. Do not replace eval() with another unsafe string-construction trick.

Rank #2
CUPEISI Android 16 Tablet 10 Inch, 20GB RAM+128GB ROM/2TB Expandable, 2.0GHz Octa-core Processor, 1280*800 HD Screen, 5G WiFi6 BT5.0, 2 in 1 Tablets with Keyboard Case Mouse Stylus, Widevine L1 Orange
  • 【2026 Newest Android 16 Tablet 】 The CUPEISI tablet equipped with the latest android 16 operating system. Powerful 2.0Ghz Octa-core processor, run smoother when open apps and loading the pages. Tablet passed the GMS certification, you can download kids apps from Google play. This tablet support widevine L1, Netflix. 
  • 【20GB RAM+128GB ROM+2TB Expansion】 Android 16 tablet comes with 20GB RAM (4GB fixed memory, 16GB virtual memory) 128GB ROM capacity and 2TB Micro SD card expansion (Micro SD card not included), large storage meets your daily entertainment and work what you need, for example, store photos, videos, songs, e-books and important files.
  • 【Portable 2-in-1 Tablet PC】 Our CP31M tablet has passed GMS certification, tablet comes with bluetooth keyboard, wireless mouse and foldable protective case, it can flexibly turn tablet into a laptop mode or computer mode. By connecting the keyboard and wireless mouse through Bluetooth, it becomes an ultra portable mini laptop, perfect for home, school, and office use. Enables you to work and learn efficiently and quickly handle daily tasks, offers you limitless features and capabilities
  • 【10.1 in HD Screen and HD Lens】 The stunning 10 In eye protection full screen has a larger visual area and a wider visual field, adopts a 1280*800 IPS HD touch screen, whether you play games, watch movies, read, take notes and work, it can bring you immersive visual. The tablet 10" inch equipped with a 8MP rear camera with auto focus and flash, shooting is equally clear during the day and night, Capture Your Wonderful Moments. 2MP front camera bring excellent clarity during video calls enjoyment.
  • 【2.4G + 5G Dual WIFI + Bluetooth 5.0】 These two features are definitely the best combination if you choose this Android tablet from CUPEISI. With 5G WIFI (which also supports 2.4G WIFI), you can watch smoother Tiktok short videos, live streaming and more on the 10.1 Inch tablet. Bluetooth 5.0 connectivity is more stable and faster.

Floating-point results

JavaScript uses IEEE 754 double-precision numbers for ordinary calculations. As a result, 0.1 + 0.2 can internally produce approximately 0.30000000000000004. This tutorial rounds the displayed result to ten decimal places:

String(Number(value.toFixed(10)))

That is display formatting, not exact decimal arithmetic. A financial calculator should use an appropriate decimal arithmetic strategy instead of relying on ordinary binary floating-point numbers.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Optional keyboard support

Add this code after the click listener to support number keys, operators, Enter, Escape, and Backspace:

document.addEventListener("keydown", (event) => {
  if (/^d$/.test(event.key)) {
    inputNumber(event.key);
    return;
  }

  if (event.key === ".") {
    inputDecimal();
    return;
  }

  if (["+", "-", "*", "/"].includes(event.key)) {
    handleOperator(event.key);
    return;
  }

  if (event.key === "Enter" || event.key === "=") {
    event.preventDefault();
    handleEquals();
    return;
  }

  if (event.key === "Escape") {
    resetCalculator();
    return;
  }

  if (event.key === "Backspace") {
    deleteLastCharacter();
  }
});

Keyboard support supplements the buttons; it should not turn the display into an unrestricted text field. Buttons remain important for mouse, touch, focus, and screen-reader users.

Accessibility and usability checklist

  • Use actual buttons rather than clickable <div> elements.
  • Keep a visible :focus-visible outline.
  • Use an <output> element or a properly labeled read-only input for the result.
  • Use aria-live="polite" so result changes can be announced without aggressively interrupting assistive technology.
  • Give symbols such as ×, ÷, and − accessible names.
  • Maintain sufficient color contrast.
  • Do not communicate an error through color alone; display the text Error.
  • Test navigation with Tab, Enter, Space, Escape, and Backspace.

Known limitations and behavior

  • Sequential evaluation: chained input is calculated one operation at a time. 2 + 3 × 4 can become (2 + 3) × 4 = 20, not 14.
  • Parentheses: unsupported; they require an expression parser.
  • Negative values: subtraction can produce a negative result, but this interface does not include a dedicated sign-toggle control.
  • Repeated Equals: Equals does not repeat the previous operation because the example clears the operator after calculating.
  • Scientific notation: not entered through the interface.
  • Large results: non-finite values become Error, while very long finite values may still require improved formatting.

Troubleshooting

Buttons appear but do nothing

Check that script.js is in the same folder, the src filename matches, and the browser console has no syntax errors. Also verify the display and calculator-keys IDs.

querySelector() returns null

Usually the selector is misspelled or the script ran before the element existed. Keep defer in the head, move the script to the end of the body, or wait for DOMContentLoaded.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Division shows Infinity

Make sure the divisor check is present in calculate(). The intended recovery is a visible Error state followed by a new number or Clear.

Decimal input is malformed

Ensure inputDecimal() checks !currentValue.includes("."). Do not use parseFloat() as validation: it can accept a valid prefix and silently ignore invalid trailing characters.

The result has too many digits

Use display formatting such as toFixed(10), while remembering that formatting does not provide exact financial or scientific precision.

Publish the calculator

Test locally first by opening index.html. For a quick public demo, CodePen’s free plan supports public Pens and up to three files, although free work is public. For a longer-term learning project, place the three files in a GitHub repository and publish the static site with GitHub Pages. GitHub documents Pages availability for public repositories on its Free plan; check the current GitHub plan documentation for private-repository and organization-specific conditions.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

StackBlitz and CodeSandbox are useful when the project grows into a larger JavaScript application, but their cloud tooling is unnecessary for this dependency-free calculator. No paid plan materially improves the basic exercise.

Three calculator designs

  1. Two-number calculator: Two inputs and one selected operation. This is the easiest design for learning basic DOM events.
  2. Sequential button calculator: The state-machine approach used here. It is a good balance of usability and understandable JavaScript.
  3. Expression calculator: Supports complete expressions, parentheses, and precedence, but needs tokenization and parsing rather than blindly evaluating a string.

Once this version works, useful next projects include a tip calculator, unit converter, mortgage calculator, expense tracker, or scientific calculator backed by a real parser.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.