Interactive Calculator

Build a sleek, fully functional calculator with arithmetic operations, decimal support, backspace, and calculation history.

Difficulty: Beginner Est. Time: 30 mins Vanilla JS (ES6+) Includes HTML, CSS, JS & README (.ZIP)
Live UI Runner

Interactive Calculator

live-sandbox://calculator

1. Project Overview & Features

The Interactive Calculator is an essential frontend project designed to help you bridge the gap between learning JavaScript syntax and building interactive, responsive web applications.

Key Features Implemented:

  • Standard operations: addition, subtraction, multiplication, division, percentage
  • Decimal point and sign inversion (+/-)
  • Backspace (DEL) and All Clear (AC) actions
  • Keyboard input support for digits and operators
  • Calculation history tape with one-click reuse

2. Step-by-Step Code Walkthrough

A Step 1: Semantic HTML Structure

We structure the interface using clean, semantic HTML elements. This ensures maximum accessibility and provides intuitive hooks for CSS classes and JavaScript query selectors.

HTML (index.html)
<div class="calculator-container">
  <div class="calc-header">
    <span class="calc-title">JS Calculator</span>
    <button id="toggle-history" class="icon-btn" title="View History"><i class="fas fa-history"></i></button>
  </div>
  <div class="calc-screen">
    <div id="calc-history-preview" class="history-preview"></div>
    <div id="calc-display" class="main-display">0</div>
  </div>
  <div id="history-panel" class="history-panel hidden">
    <div class="history-header">
      <span>History</span>
      <button id="clear-history" class="text-btn">Clear</button>
    </div>
    <div id="history-list" class="history-list"></div>
  </div>
  <div class="calc-grid">
    <button class="btn btn-action" data-action="clear">AC</button>
    <button class="btn btn-action" data-action="delete"><i class="fas fa-backspace"></i></button>
    <button class="btn btn-action" data-action="percent">%</button>
    <button class="btn btn-operator" data-op="/">Γ·</button>
    
    <button class="btn btn-num" data-num="7">7</button>
    <button class="btn btn-num" data-num="8">8</button>
    <button class="btn btn-num" data-num="9">9</button>
    <button class="btn btn-operator" data-op="*">Γ—</button>
    
    <button class="btn btn-num" data-num="4">4</button>
    <button class="btn btn-num" data-num="5">5</button>
    <button class="btn btn-num" data-num="6">6</button>
    <button class="btn btn-operator" data-op="-">βˆ’</button>
    
    <button class="btn btn-num" data-num="1">1</button>
    <button class="btn btn-num" data-num="2">2</button>
    <button class="btn btn-num" data-num="3">3</button>
    <button class="btn btn-operator" data-op="+">+</button>
    
    <button class="btn btn-action" data-action="negate">Β±</button>
    <button class="btn btn-num" data-num="0">0</button>
    <button class="btn btn-num" data-num=".">.</button>
    <button class="btn btn-equals" data-action="calculate">=</button>
  </div>
</div>

B Step 2: Styling & Responsive CSS

Modern CSS flexbox and grid layouts are used to make the UI look polished, centered, and responsive across desktop, tablet, and mobile screens.

CSS (style.css)
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
body {
  background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.calculator-container {
  background: #1e293b;
  border: 1px solid #334155;
  border-radius: 20px;
  width: 100%;
  max-width: 360px;
  padding: 20px;
  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4);
  position: relative;
  overflow: hidden;
}
.calc-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
}
.calc-title {
  color: #94a3b8;
  font-size: 0.85rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 1px;
}
.icon-btn {
  background: transparent;
  border: none;
  color: #94a3b8;
  font-size: 1rem;
  cursor: pointer;
  padding: 4px 8px;
  border-radius: 6px;
  transition: all 0.2s;
}
.icon-btn:hover {
  background: #334155;
  color: #38bdf8;
}
.calc-screen {
  background: #0f172a;
  border-radius: 12px;
  padding: 16px;
  margin-bottom: 18px;
  text-align: right;
  border: 1px solid #1e293b;
  min-height: 84px;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}
.history-preview {
  color: #64748b;
  font-size: 0.85rem;
  min-height: 18px;
  margin-bottom: 4px;
  word-break: break-all;
}
.main-display {
  color: #f8fafc;
  font-size: 2rem;
  font-weight: 700;
  overflow-x: auto;
  white-space: nowrap;
}
.calc-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 10px;
}
.btn {
  border: none;
  outline: none;
  padding: 16px 0;
  font-size: 1.25rem;
  font-weight: 600;
  border-radius: 12px;
  cursor: pointer;
  transition: all 0.15s ease;
  user-select: none;
}
.btn:active {
  transform: scale(0.95);
}
.btn-num {
  background: #334155;
  color: #f8fafc;
}
.btn-num:hover {
  background: #475569;
}
.btn-action {
  background: #475569;
  color: #38bdf8;
}
.btn-action:hover {
  background: #64748b;
}
.btn-operator {
  background: #0284c7;
  color: #ffffff;
}
.btn-operator:hover {
  background: #0369a1;
}
.btn-equals {
  background: #10b981;
  color: #ffffff;
  font-weight: 700;
}
.btn-equals:hover {
  background: #059669;
}
.history-panel {
  position: absolute;
  top: 60px;
  left: 20px;
  right: 20px;
  bottom: 20px;
  background: rgba(15, 23, 42, 0.96);
  backdrop-filter: blur(8px);
  border-radius: 12px;
  padding: 16px;
  display: flex;
  flex-direction: column;
  z-index: 10;
  transition: opacity 0.2s ease, transform 0.2s ease;
}
.history-panel.hidden {
  opacity: 0;
  pointer-events: none;
  transform: translateY(10px);
}
.history-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  color: #f8fafc;
  font-weight: 600;
  border-bottom: 1px solid #334155;
  padding-bottom: 8px;
  margin-bottom: 10px;
}
.text-btn {
  background: none;
  border: none;
  color: #f87171;
  cursor: pointer;
  font-size: 0.8rem;
}
.history-list {
  flex: 1;
  overflow-y: auto;
}
.history-item {
  padding: 8px 6px;
  border-bottom: 1px solid #1e293b;
  color: #94a3b8;
  font-size: 0.9rem;
  cursor: pointer;
  display: flex;
  justify-content: space-between;
}
.history-item:hover {
  color: #38bdf8;
}

C Step 3: JavaScript Logic & Event Handling

Here is the complete JavaScript code handling the state, event listeners, mathematical logic, and dynamic DOM rendering:

JavaScript (script.js)
// State variables
let currentInput = '0';
let previousInput = '';
let operation = null;
let shouldResetDisplay = false;
let historyRecords = [];

const display = document.getElementById('calc-display');
const historyPreview = document.getElementById('calc-history-preview');
const historyPanel = document.getElementById('history-panel');
const historyList = document.getElementById('history-list');

// Update UI display
function updateDisplay() {
  display.textContent = currentInput;
  if (operation !== null) {
    const opSymbols = { '+': '+', '-': 'βˆ’', '*': 'Γ—', '/': 'Γ·' };
    historyPreview.textContent = `${previousInput} ${opSymbols[operation] || operation}`;
  } else {
    historyPreview.textContent = '';
  }
}

// Append digit or decimal
function appendNumber(num) {
  if (shouldResetDisplay) {
    currentInput = '';
    shouldResetDisplay = false;
  }
  if (num === '.' && currentInput.includes('.')) return;
  if (currentInput === '0' && num !== '.') {
    currentInput = num;
  } else {
    currentInput += num;
  }
  updateDisplay();
}

// Choose operator
function setOperation(op) {
  if (operation !== null && !shouldResetDisplay) {
    calculate();
  }
  previousInput = currentInput;
  operation = op;
  shouldResetDisplay = true;
  updateDisplay();
}

// Perform calculation
function calculate() {
  if (operation === null || shouldResetDisplay) return;
  
  const prev = parseFloat(previousInput);
  const current = parseFloat(currentInput);
  if (isNaN(prev) || isNaN(current)) return;
  
  let result = 0;
  switch (operation) {
    case '+': result = prev + current; break;
    case '-': result = prev - current; break;
    case '*': result = prev * current; break;
    case '/': 
      if (current === 0) {
        currentInput = 'Error';
        operation = null;
        updateDisplay();
        shouldResetDisplay = true;
        return;
      }
      result = prev / current; 
      break;
    default: return;
  }
  
  // Format result to prevent floating point anomalies
  result = Math.round(result * 100000000) / 100000000;
  
  // Add to history
  const opSymbols = { '+': '+', '-': 'βˆ’', '*': 'Γ—', '/': 'Γ·' };
  const historyEntry = `${prev} ${opSymbols[operation]} ${current} = ${result}`;
  historyRecords.unshift(historyEntry);
  renderHistory();
  
  currentInput = result.toString();
  operation = null;
  shouldResetDisplay = true;
  updateDisplay();
}

// Delete last digit
function deleteDigit() {
  if (shouldResetDisplay) return;
  if (currentInput.length === 1 || (currentInput.length === 2 && currentInput.startsWith('-'))) {
    currentInput = '0';
  } else {
    currentInput = currentInput.slice(0, -1);
  }
  updateDisplay();
}

// Clear all
function clearAll() {
  currentInput = '0';
  previousInput = '';
  operation = null;
  shouldResetDisplay = false;
  updateDisplay();
}

// Render history list
function renderHistory() {
  historyList.innerHTML = '';
  if (historyRecords.length === 0) {
    historyList.innerHTML = '<p style="color:#64748b;font-size:0.85rem;padding:8px 0">No calculation history yet.</p>';
    return;
  }
  historyRecords.forEach(item => {
    const div = document.createElement('div');
    div.className = 'history-item';
    div.textContent = item;
    div.addEventListener('click', () => {
      const parts = item.split(' = ');
      if (parts[1]) {
        currentInput = parts[1];
        updateDisplay();
        historyPanel.classList.add('hidden');
      }
    });
    historyList.appendChild(div);
  });
}

// Event Delegation for Buttons
document.querySelector('.calc-grid').addEventListener('click', (e) => {
  const btn = e.target.closest('button');
  if (!btn) return;
  
  if (btn.dataset.num !== undefined) {
    appendNumber(btn.dataset.num);
  } else if (btn.dataset.op !== undefined) {
    setOperation(btn.dataset.op);
  } else if (btn.dataset.action) {
    switch (btn.dataset.action) {
      case 'calculate': calculate(); break;
      case 'clear': clearAll(); break;
      case 'delete': deleteDigit(); break;
      case 'percent': 
        currentInput = (parseFloat(currentInput) / 100).toString();
        updateDisplay();
        break;
      case 'negate':
        currentInput = (parseFloat(currentInput) * -1).toString();
        updateDisplay();
        break;
    }
  }
});

// History Toggle
document.getElementById('toggle-history').addEventListener('click', () => {
  historyPanel.classList.toggle('hidden');
});
document.getElementById('clear-history').addEventListener('click', () => {
  historyRecords = [];
  renderHistory();
});

// Keyboard Support
window.addEventListener('keydown', (e) => {
  if (e.key >= '0' && e.key <= '9' || e.key === '.') appendNumber(e.key);
  if (['+', '-', '*', '/'].includes(e.key)) setOperation(e.key);
  if (e.key === 'Enter' || e.key === '=') { e.preventDefault(); calculate(); }
  if (e.key === 'Backspace') deleteDigit();
  if (e.key === 'Escape') clearAll();
});

renderHistory();

3. Core JavaScript Concepts You Learned

DOM Manipulation

Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.

Event Listeners

Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.

eval & Math Logic

Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.

Keyboard Events

Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.

State Management

Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.

4. How to Run Locally on Your Computer

  1. Download the project ZIP: Click the Download Project (.ZIP) button at the top or in the live runner to save calculator-project.zip.
  2. Extract the archive: Unzip the downloaded file. Inside you will find separate index.html, style.css, script.js, index-standalone.html, and a comprehensive README.md.
  3. Open in any browser: Simply double-click index.html (or index-standalone.html) to open and test immediately in Chrome, Firefox, Safari, or Edge.
  4. Edit in Visual Studio Code: Open the extracted folder in VS Code. Right-click index.html and select "Open with Live Server" to enjoy instant live reloading as you modify HTML, CSS, or JS!

Bonus Challenges for Learners

Ready to level up? Try adding these extra features on your own:

  • Add sound effects or audio feedback for user button clicks.
  • Add custom theme color customizer with CSS variables.
  • Export the results to PDF or copy as a formatted text summary.

Other Beginner Projects You Might Like

Beginner15 mins
Math & Calculators

BMI Health Calculator

Calculate Body Mass Index with metric (kg/cm) and imperial (lbs/ft) unit modes, gauge needle indicator, and health category tips.

Mathematical FormulasUnit ConversionsDOM Visual Gauge Needles+1