Expense Tracker with Simple Analytics

Beginner 7 views Jul 25, 2026

Introduction

Keeping track of where your money goes is one of the simplest habits that can make a big financial difference — and building a tool to do it is one of the best ways to practice real-world JavaScript. In this beginner-friendly tutorial, you'll build an Expense Tracker with Simple Analytics using only HTML, CSS, and JavaScript. You'll learn how to add income and expense transactions, calculate a running balance, and visualize your spending by category with a simple pie chart — all without a backend, since everything is saved directly in the browser using the LocalStorage API.

Project Overview

This project is a single-page web application. Users enter a transaction description, an amount, a type (income or expense), and a category. The app instantly updates the total balance, total income, and total expenses at the top of the page. Every transaction appears in a list below, and a Chart.js pie chart breaks down expenses by category, giving users a quick visual sense of where their money is going.

Features

  • Add income and expense transactions with description, amount, and category
  • Automatic calculation of balance, total income, and total expenses
  • Category-wise spending breakdown shown as a pie chart
  • Delete individual transactions with instant recalculation
  • Color-coded transaction list (green for income, red for expenses)
  • Data persistence using the browser's LocalStorage — no backend required
  • Fully responsive layout that works on mobile, tablet, and desktop
  • Clean, beginner-friendly, well-commented code

Technologies Used

  • HTML5 – Page structure and semantic markup
  • CSS3 – Styling, Flexbox layout, and responsive design
  • JavaScript (ES6) – App logic, DOM manipulation, and localStorage handling
  • Chart.js (CDN) – Rendering the category-wise spending pie chart

Folder Structure

Here's how we'll organize the project files, keeping HTML, CSS, and JavaScript neatly separated:

PLAINTEXT
expense-tracker/
│
├── index.html          → Main HTML file
├── css/
│   └── style.css       → All styling and responsive rules
└── js/
    └── script.js        → App logic (transactions, balance, chart)

Step 1: Create the Project Folder Structure

Create a folder named expense-tracker. Inside it, create a css folder and a js folder as shown above. This separation keeps our code organized and easy to maintain, even as the project grows.

Step 2: Build the HTML Structure

Create a file named index.html and add the code below. This sets up a summary section for balance/income/expenses, a form to add transactions, a list to display them, and a canvas for our analytics chart.

HTML
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <!-- Makes the page responsive on mobile devices -->
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Expense Tracker with Simple Analytics</title>

  <!-- Our custom stylesheet -->
  <link rel="stylesheet" href="css/style.css" />
</head>
<body>

  <!-- Page Header -->
  <header class="app-header">
    <h1>💰 Expense Tracker</h1>
    <p>Track your income and expenses with ease.</p>
  </header>

  <main class="container">

    <!-- Summary cards showing balance, income, and expenses -->
    <section class="summary-section">
      <div class="summary-card">
        <h3>Balance</h3>
        <p id="balanceAmount">₹0.00</p>
      </div>
      <div class="summary-card income">
        <h3>Income</h3>
        <p id="incomeAmount">₹0.00</p>
      </div>
      <div class="summary-card expense">
        <h3>Expenses</h3>
        <p id="expenseAmount">₹0.00</p>
      </div>
    </section>

    <!-- Form to add a new transaction -->
    <section class="add-transaction-section">
      <h2>Add Transaction</h2>
      <form id="transactionForm">
        <input
          type="text"
          id="descInput"
          placeholder="Description (e.g. Groceries)"
          required
        />
        <input
          type="number"
          id="amountInput"
          placeholder="Amount"
          min="0.01"
          step="0.01"
          required
        />
        <select id="typeInput">
          <option value="income">Income</option>
          <option value="expense">Expense</option>
        </select>
        <select id="categoryInput">
          <option value="Food">Food</option>
          <option value="Transport">Transport</option>
          <option value="Shopping">Shopping</option>
          <option value="Bills">Bills</option>
          <option value="Salary">Salary</option>
          <option value="Other">Other</option>
        </select>
        <button type="submit">Add Transaction</button>
      </form>
    </section>

    <!-- List of transactions will be injected here by JavaScript -->
    <section class="transaction-list-section">
      <h2>Transaction History</h2>
      <ul id="transactionList" class="transaction-list"></ul>
    </section>

    <!-- Analytics chart section -->
    <section class="analytics-section">
      <h2>Spending by Category</h2>
      <canvas id="analyticsChart"></canvas>
    </section>

  </main>

  <footer class="app-footer">
    <p>Made with ❤️ using HTML, CSS & JavaScript | MyRiteBook</p>
  </footer>

  <!-- Chart.js library loaded from CDN, used to draw the analytics chart -->
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>

  <!-- Our own app logic, loaded last so the DOM is ready -->
  <script src="js/script.js"></script>
</body>
</html>

Step 3: Style the App with CSS (Responsive Design)

Create css/style.css. We'll use CSS variables for theming, a Flexbox/Grid layout for the summary cards, and a media query so everything adapts neatly to smaller screens.

CSS
/* ===== CSS Variables for easy theme control ===== */
:root {
  --primary-color: #4f46e5;
  --income-color: #22c55e;
  --expense-color: #ef4444;
  --bg-color: #f4f5f9;
  --card-bg: #ffffff;
  --text-color: #1f2937;
  --muted-color: #6b7280;
  --radius: 10px;
}

/* ===== Reset default browser spacing ===== */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: "Segoe UI", Arial, sans-serif;
  background-color: var(--bg-color);
  color: var(--text-color);
  line-height: 1.5;
}

/* ===== Header ===== */
.app-header {
  background-color: var(--primary-color);
  color: #fff;
  text-align: center;
  padding: 30px 15px;
}

/* ===== Main container keeps content centered and readable ===== */
.container {
  max-width: 700px;
  margin: 0 auto;
  padding: 20px;
}

/* ===== Summary Cards ===== */
.summary-section {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
  margin-bottom: 25px;
}

.summary-card {
  background: var(--card-bg);
  padding: 16px;
  border-radius: var(--radius);
  text-align: center;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}

.summary-card h3 {
  font-size: 13px;
  color: var(--muted-color);
  margin-bottom: 6px;
  font-weight: 500;
}

.summary-card p {
  font-size: 18px;
  font-weight: 700;
}

.summary-card.income p {
  color: var(--income-color);
}

.summary-card.expense p {
  color: var(--expense-color);
}

/* ===== Add Transaction Form ===== */
.add-transaction-section {
  background: var(--card-bg);
  padding: 20px;
  border-radius: var(--radius);
  margin-bottom: 25px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}

.add-transaction-section h2 {
  font-size: 17px;
  margin-bottom: 14px;
}

#transactionForm {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

#transactionForm input,
#transactionForm select {
  flex: 1;
  min-width: 140px;
  padding: 10px 12px;
  border: 1px solid #ccc;
  border-radius: var(--radius);
  font-size: 14px;
}

#transactionForm button {
  background-color: var(--primary-color);
  color: #fff;
  border: none;
  padding: 10px 18px;
  border-radius: var(--radius);
  cursor: pointer;
  font-size: 14px;
  width: 100%;
}

#transactionForm button:hover {
  opacity: 0.9;
}

/* ===== Transaction List ===== */
.transaction-list-section h2 {
  font-size: 17px;
  margin-bottom: 12px;
}

.transaction-list {
  list-style: none;
}

.transaction-card {
  background: var(--card-bg);
  padding: 12px 16px;
  border-radius: var(--radius);
  margin-bottom: 10px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  border-left: 4px solid var(--income-color);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}

.transaction-card.expense {
  border-left-color: var(--expense-color);
}

.transaction-info span {
  display: block;
}

.transaction-info .desc {
  font-size: 15px;
  font-weight: 600;
}

.transaction-info .category {
  font-size: 12px;
  color: var(--muted-color);
}

.transaction-amount {
  font-weight: 700;
  margin-right: 12px;
}

.transaction-amount.income {
  color: var(--income-color);
}

.transaction-amount.expense {
  color: var(--expense-color);
}

.btn-delete {
  background-color: var(--expense-color);
  color: #fff;
  border: none;
  padding: 6px 10px;
  border-radius: var(--radius);
  cursor: pointer;
  font-size: 12px;
}

/* ===== Analytics Section ===== */
.analytics-section {
  background: var(--card-bg);
  padding: 20px;
  border-radius: var(--radius);
  margin-top: 20px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}

.analytics-section h2 {
  font-size: 17px;
  margin-bottom: 14px;
}

/* ===== Footer ===== */
.app-footer {
  text-align: center;
  padding: 20px;
  font-size: 13px;
  color: var(--muted-color);
}

/* ===== Responsive Design for smaller screens ===== */
@media (max-width: 600px) {
  .summary-section {
    grid-template-columns: 1fr;
  }

  #transactionForm input,
  #transactionForm select {
    min-width: 100%;
  }

  .transaction-card {
    flex-wrap: wrap;
    gap: 8px;
  }
}

Step 4: Add Functionality with JavaScript

Create js/script.js. This file handles adding transactions, calculating the balance, saving data to localStorage, and updating the category-wise analytics chart. Every function is commented to explain exactly what it does.

JAVASCRIPT
// ================================================================
    //  EXPENSE TRACKER — Complete Beginner Project
    //  Data is stored in localStorage using the key "expenseTrackerData"
    //  Each transaction: { id, desc, amount, type, category }
    // ================================================================

    (function() {
      'use strict';

      // ----- CONSTANTS & STATE -----
      const STORAGE_KEY = 'expenseTrackerData';
      let transactions = JSON.parse(localStorage.getItem(STORAGE_KEY)) || [];
      let analyticsChart = null;

      // DOM refs
      const transactionForm = document.getElementById('transactionForm');
      const descInput = document.getElementById('descInput');
      const amountInput = document.getElementById('amountInput');
      const typeInput = document.getElementById('typeInput');
      const categoryInput = document.getElementById('categoryInput');
      const transactionListEl = document.getElementById('transactionList');
      const balanceAmountEl = document.getElementById('balanceAmount');
      const incomeAmountEl = document.getElementById('incomeAmount');
      const expenseAmountEl = document.getElementById('expenseAmount');
      const chartCanvas = document.getElementById('analyticsChart');

      // ----- HELPERS -----
      function saveTransactions() {
        localStorage.setItem(STORAGE_KEY, JSON.stringify(transactions));
      }

      function formatCurrency(amount) {
        return '₹' + amount.toFixed(2);
      }

      function escapeHtml(text) {
        const div = document.createElement('div');
        div.textContent = text;
        return div.innerHTML;
      }

      // ----- CORE CRUD -----
      function addTransaction(desc, amount, type, category) {
        const newTransaction = {
          id: Date.now(),
          desc: desc.trim(),
          amount: amount,
          type: type,
          category: category
        };
        transactions.push(newTransaction);
        saveTransactions();
        renderAll();
      }

      function deleteTransaction(id) {
        transactions = transactions.filter(t => t.id !== id);
        saveTransactions();
        renderAll();
      }

      // ----- CALCULATIONS -----
      function calculateSummary() {
        let totalIncome = 0;
        let totalExpense = 0;
        transactions.forEach(t => {
          if (t.type === 'income') totalIncome += t.amount;
          else totalExpense += t.amount;
        });
        return { totalIncome, totalExpense, balance: totalIncome - totalExpense };
      }

      function calculateCategoryTotals() {
        const totals = {};
        transactions
          .filter(t => t.type === 'expense')
          .forEach(t => {
            if (!totals[t.category]) totals[t.category] = 0;
            totals[t.category] += t.amount;
          });
        return totals;
      }

      // ----- RENDER FUNCTIONS -----
      function renderSummary() {
        const { totalIncome, totalExpense, balance } = calculateSummary();
        balanceAmountEl.textContent = formatCurrency(balance);
        incomeAmountEl.textContent = formatCurrency(totalIncome);
        expenseAmountEl.textContent = formatCurrency(totalExpense);
      }

      function renderTransactions() {
        transactionListEl.innerHTML = '';
        if (transactions.length === 0) {
          transactionListEl.innerHTML ='
          <p>✨ No transactions yet. Add one above!</p>
';
          return;
        }
        const sorted = [...transactions].reverse();
        sorted.forEach(t => {
          const li = document.createElement('li');
          li.className = 'transaction-card ' + (t.type === 'expense' ? 'expense' : '');
          const sign = t.type === 'income' ? '+' : '-';
          li.innerHTML = `
            <div class="transaction-info"><span class="desc">${escapeHtml(t.desc)}</span> <span class="category">${escapeHtml(t.category)}</span></div>
<div><span class="transaction-amount ${t.type}">${sign} ${formatCurrency(t.amount)}</span> <button class="btn-delete" data-id="${t.id}">Delete</button></div>
          `;
          transactionListEl.appendChild(li);
        });
      }

      function renderChart() {
        const categoryTotals = calculateCategoryTotals();
        const labels = Object.keys(categoryTotals);
        const data = Object.values(categoryTotals);

        if (analyticsChart) {
          analyticsChart.destroy();
          analyticsChart = null;
        }

        if (labels.length === 0) {
          analyticsChart = new Chart(chartCanvas, {
            type: 'pie',
            data: {
              labels: ['No expenses yet'],
              datasets: [{ data: [1], backgroundColor: ['#d1d5db'] }]
            },
            options: {
              responsive: true,
              plugins: { legend: { position: 'bottom' } }
            }
          });
          return;
        }

        const colors = ['#4f46e5', '#22c55e', '#ef4444', '#f59e0b', '#0ea5e9', '#8b5cf6'];
        analyticsChart = new Chart(chartCanvas, {
          type: 'pie',
          data: {
            labels: labels,
            datasets: [{
              data: data,
              backgroundColor: colors.slice(0, labels.length),
              borderWidth: 1
            }]
          },
          options: {
            responsive: true,
            plugins: {
              legend: { position: 'bottom' }
            }
          }
        });
      }

      function renderAll() {
        renderSummary();
        renderTransactions();
        renderChart();
      }

      // ----- EVENT LISTENERS -----
      transactionForm.addEventListener('submit', e => {
        e.preventDefault();
        const desc = descInput.value.trim();
        const amount = parseFloat(amountInput.value);
        const type = typeInput.value;
        const category = categoryInput.value;

        if (desc === '' || isNaN(amount) || amount <= 0) {
          return;
        }

        addTransaction(desc, amount, type, category);
        descInput.value = '';
        amountInput.value = '';
        descInput.focus();
      });

      transactionListEl.addEventListener('click', e => {
        const target = e.target.closest('.btn-delete');
        if (!target) return;
        const id = Number(target.dataset.id);
        if (isNaN(id)) return;
        if (confirm('Delete this transaction?')) {
          deleteTransaction(id);
        }
      });

      // ----- INITIAL RENDER -----
      renderAll();

    })();

Step 5: How the Balance Calculation Works

The calculateSummary() function loops through every transaction once, adding each amount to either totalIncome or totalExpense depending on its type. The balance is simply income minus expenses. This function is called every time a transaction is added or deleted, so the summary cards always stay accurate and up to date.

Step 6: How the Category Chart Works

The calculateCategoryTotals() function filters out only expense transactions and groups their amounts by category using a plain JavaScript object as a lookup table. The renderChart() function then feeds these totals into Chart.js as a pie chart, giving the user an instant visual breakdown of where their money is going. The chart is destroyed and recreated on every update so it never shows stale data.

Step 7: Test Your Expense Tracker

Open index.html in your browser and try the following:

  • Add a few income entries like "Salary" and expense entries like "Groceries" or "Fuel"
  • Watch the balance, income, and expense totals update instantly
  • Add multiple expenses in different categories and see the pie chart update
  • Delete a transaction and confirm the totals and chart recalculate correctly
  • Refresh the page — your data should still be there, thanks to localStorage
  • Resize your browser or open it on your phone to check the responsive layout

Possible Enhancements

Once you're comfortable with the basics, try extending the project with these ideas:

  • Add date filtering to view expenses by day, week, or month
  • Add a monthly budget limit with a warning when you're close to exceeding it
  • Export transaction history as a CSV file
  • Add custom categories instead of a fixed dropdown list
  • Sync data with a backend (PHP/MySQL) instead of localStorage for multi-device access

Conclusion

In this project, you built a fully functional Expense Tracker with Simple Analytics using only HTML, CSS, and JavaScript. You learned how to manage structured data with arrays of objects, perform running calculations, persist data with localStorage, and visualize spending patterns using Chart.js. This project is a practical, portfolio-worthy piece that mirrors real-world personal finance apps while staying approachable for beginners.

Technologies Used

HTML5 CSS3 JavaScript Chart.js LocalStorage API

Live Output

See the project in action below. This is the live preview of the code you just learned.

Output Preview