Back to Projects Expert

Expense Tracker

Build a personal finance app that tracks income and expenses — and saves the data even after you close the browser using localStorage.

HTML, CSS, JavaScript ~1-2 hours to build Free source code

What You'll Learn

  • How to store and retrieve data using the browser's localStorage
  • How to serialize objects to JSON with JSON.stringify() and parse them back
  • How to add and remove items from a persistent list
  • How to calculate totals and balances by filtering arrays
  • How to re-render a list every time data changes (the core UI update pattern)

How It Works

localStorage keeps data after closing

Unlike regular variables that disappear when you close the browser, localStorage keeps your data saved permanently. We convert our transactions array to a JSON string to save it, then parse it back on page load.

Transactions have a type: income or expense

Each transaction object has three properties: description, amount, and type. We use the type to calculate totals separately — adding income and subtracting expenses to get the balance.

Re-render on every change

Every time we add or delete a transaction, we recalculate all totals and re-draw the entire list. This 'update data → re-render UI' pattern is the foundation of modern web apps like React.

Source Code

HTML Structure

HTML
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Expense Tracker</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <div class="tracker">
    <h1>💰 Expense Tracker</h1>
    <div class="summary">
      <div class="summary-card balance">
        <span>Balance</span><strong id="balance">$0.00</strong>
      </div>
      <div class="summary-card income">
        <span>Income</span><strong id="income">$0.00</strong>
      </div>
      <div class="summary-card expense">
        <span>Expenses</span><strong id="expenses">$0.00</strong>
      </div>
    </div>
    <form id="transForm">
      <input type="text" id="desc" placeholder="Description (e.g. Coffee)" required>
      <div class="form-row">
        <input type="number" id="amount" placeholder="Amount" step="0.01" min="0.01" required>
        <select id="type">
          <option value="expense">Expense</option>
          <option value="income">Income</option>
        </select>
      </div>
      <button type="submit">Add Transaction</button>
    </form>
    <h3>Transaction History</h3>
    <ul id="transactionList"></ul>
  </div>
  <script src="script.js"></script>
</body>
</html>

CSS Styling

CSS
* { margin:0; padding:0; box-sizing:border-box; }
body { min-height:100vh; background:linear-gradient(135deg,#4a1080,#8e44ad);
  display:flex; align-items:flex-start; justify-content:center;
  padding:30px 16px; font-family:'Segoe UI',sans-serif; }
.tracker { background:white; border-radius:20px; padding:32px; width:100%; max-width:480px;
  box-shadow:0 20px 60px rgba(0,0,0,0.3); }
h1 { text-align:center; font-size:1.6rem; color:#25265e; margin-bottom:24px; }
.summary { display:grid; grid-template-columns:1fr 1fr 1fr; gap:12px; margin-bottom:24px; }
.summary-card { background:#f8f9fa; border-radius:12px; padding:16px 12px; text-align:center; }
.summary-card span { display:block; font-size:0.75rem; color:#888;
  text-transform:uppercase; letter-spacing:0.5px; margin-bottom:4px; }
.summary-card strong { font-size:1.1rem; font-weight:800; }
.summary-card.balance strong { color:#25265e; }
.summary-card.income strong { color:#27ae60; }
.summary-card.expense strong { color:#e74c3c; }
form input, form select { width:100%; padding:12px 14px; border:2px solid #e0e0e0;
  border-radius:8px; font-size:0.95rem; outline:none; margin-bottom:12px;
  transition:border-color 0.2s; background:white; color:#333; }
form input:focus, form select:focus { border-color:#8e44ad; }
.form-row { display:grid; grid-template-columns:1fr 1fr; gap:12px; }
.form-row input, .form-row select { margin-bottom:0; }
form button { width:100%; padding:14px; background:#8e44ad; color:white; border:none;
  border-radius:8px; font-weight:700; font-size:1rem; cursor:pointer; margin-top:12px; }
form button:hover { background:#7d3c98; }
h3 { font-size:1rem; color:#25265e; margin:20px 0 12px; font-weight:700; }
ul { list-style:none; }
ul li { display:flex; justify-content:space-between; align-items:center;
  padding:12px 14px; border-radius:8px; margin-bottom:8px;
  background:#f8f9fa; border-left:4px solid transparent; }
ul li.income-item { border-left-color:#27ae60; }
ul li.expense-item { border-left-color:#e74c3c; }
.desc-text { flex:1; font-size:0.9rem; color:#333; }
.amount-text { font-weight:700; font-size:0.95rem; margin-right:10px; }
.amount-text.income { color:#27ae60; }
.amount-text.expense { color:#e74c3c; }
.del-btn { background:none; border:none; cursor:pointer; color:#ccc; font-size:1rem; padding:2px 6px; }
.del-btn:hover { color:#e74c3c; }

JavaScript Logic

JavaScript
let transactions = JSON.parse(localStorage.getItem('transactions')) || [];

function save() {
  localStorage.setItem('transactions', JSON.stringify(transactions));
}

function updateSummary() {
  const income = transactions.filter(t => t.type === 'income').reduce((s, t) => s + t.amount, 0);
  const expenses = transactions.filter(t => t.type === 'expense').reduce((s, t) => s + t.amount, 0);
  document.getElementById('balance').textContent = `$${(income - expenses).toFixed(2)}`;
  document.getElementById('income').textContent = `$${income.toFixed(2)}`;
  document.getElementById('expenses').textContent = `$${expenses.toFixed(2)}`;
}

function render() {
  const list = document.getElementById('transactionList');
  list.innerHTML = '';
  if (!transactions.length) {
    list.innerHTML = '

No transactions yet

'; return; } transactions.forEach((t, i) => { const li = document.createElement('li'); li.className = t.type + '-item'; li.innerHTML = `${t.description} ${t.type === 'income' ? '+' : '-'}$${t.amount.toFixed(2)} `; list.appendChild(li); }); } function del(i) { transactions.splice(i, 1); save(); updateSummary(); render(); } document.getElementById('transForm').addEventListener('submit', function(e) { e.preventDefault(); const desc = document.getElementById('desc').value.trim(); const amount = parseFloat(document.getElementById('amount').value); const type = document.getElementById('type').value; if (!desc || !amount || amount <= 0) return; transactions.unshift({ description: desc, amount, type }); save(); updateSummary(); render(); this.reset(); }); updateSummary(); render();

Download Source Code

Single ready-to-run HTML file. Open it in any browser!

Download Project

Single HTML file · All code included