Learn how to create a modern expense tracker using HTML, CSS, and JavaScript. Follow this easy step-by-step guide to build your own tracker in minutes!
Table of Contents
Managing your expenses is easier when you have the right tools. Building your own modern expense tracker using HTML, CSS, and JavaScript is a fun and rewarding project. This tracker will allow you to add, view, and delete transactions. You can also see your income, expenses, and balance, all with a clean and modern design.
Whether you're a beginner or have some coding experience, this guide will walk you through the steps to create a fully functional expense tracker.
Prerequisites
Before starting, ensure you have the following:
- Basic knowledge of HTML, CSS, and JavaScript.
- A code editor, such as Visual Studio Code.
- A modern web browser for testing.
Source Code
Step 1 (HTML Code):
The first thing we need to do is create our HTML File. We'll start with well-organized markup.
Here's a detailed explanation of each section:
1. Document Type and Metadata
<!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 href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="styles.css">
</head>
<!DOCTYPE html>
: Declares the document as an HTML5 file.<html lang="en">
: Specifies the language of the document as English.<meta charset="UTF-8">
: Sets the character encoding to UTF-8, allowing the use of special characters.<meta name="viewport" content="width=device-width, initial-scale=1.0">
: Ensures the page is responsive on mobile devices by setting the width to match the device's screen.<title>
: Specifies the title of the page as "Expense Tracker."- Google Fonts Link: Loads the "Poppins" font for styling text.
- CSS Link: Links to an external stylesheet (
styles.css
) for styling the page.
2. Body Content
<body>
<div class="container">
<h1>Expense Tracker</h1>
<body>
: Contains all the visible content of the page.<div class="container">
: A container div that wraps all the elements for layout purposes.<h1>Expense Tracker</h1>
: Displays the main heading.
3. Balance Section
<div class="balance">
<h2>Your Balance</h2>
<p id="balance">$0.00</p>
</div>
<div class="balance">
: Section for showing the total balance.<h2>Your Balance</h2>
: A subheading for the balance section.<p id="balance">$0.00</p>
: Displays the current balance, initially set to$0.00
. Theid="balance"
allows JavaScript to dynamically update this value.
4. Income and Expense Section
<div class="income-expense">
<div>
<h3>Income</h3>
<p id="income">$0.00</p>
</div>
<div class="expense">
<h3>Expenses</h3>
<p id="expense">$0.00</p>
</div>
</div>
<div class="income-expense">
: Groups the income and expense details.- Income Section: Shows total income with
id="income"
. - Expense Section: Shows total expenses with
id="expense"
. - Both values are initially set to
$0.00
and will be updated dynamically using JavaScript.
5. Add Transaction Form
<div class="form">
<h3>Add Transaction</h3>
<div class="form-control">
<label for="text">Description</label>
<input type="text" id="text" placeholder="Enter description">
</div>
<div class="form-control">
<label for="amount">Amount <small>(negative - expense, positive - income)</small></label>
<input type="number" id="amount" placeholder="Enter amount">
</div>
<button class="btn" id="add-transaction">Add Transaction</button>
</div>
<div class="form">
: Contains the form for adding transactions.- Description Input:
<label for="text">
is linked to theid="text"
input field.<input type="text">
allows users to enter a description.
- Amount Input:
<label for="amount">
is linked to theid="amount"
input field.<input type="number">
allows users to enter a numeric value for the amount.- The placeholder explains that negative values are for expenses and positive values are for income.
- Add Transaction Button:
<button>
triggers the addition of a transaction when clicked.id="add-transaction"
allows JavaScript to bind a click event.
6. Transaction History
<div class="history">
<h3>Transaction History</h3>
<table>
<thead>
<tr>
<th>Date</th>
<th>Description</th>
<th>Amount</th>
<th>Action</th>
</tr>
</thead>
<tbody id="list">
</tbody>
</table>
</div>
<div class="history">
: Displays the transaction history.<table>
: A table layout for organizing transactions.<thead>
: Contains table headers: Date, Description, Amount, and Action.<tbody id="list">
: Empty initially. JavaScript dynamically adds rows for each transaction here.
7. JavaScript Integration
<script src="script.js"></script>
- Links the
script.js
file, which handles all the dynamic functionality, such as adding transactions, updating balances, and managing the transaction history.
Step 2 (CSS Code):
Once the basic HTML structure of the expense tracker is in place, the next step is to add styling to the tracker using CSS. Below is an explanation of its structure and functionality:
1. Global Styles
body {
font-family: 'Poppins', sans-serif;
margin: 0;
padding: 0;
background: #f0f4f8;
color: #333;
}
- Font: Sets the font to 'Poppins' with a fallback to sans-serif.
- Background: Light grayish-blue (
#f0f4f8
) for a clean and modern look. - Color: Default text color is dark gray (
#333
). - Reset: Removes default margins and padding.
2. Container
.container {
max-width: 500px;
margin: 30px auto;
background: #ffffff;
padding: 20px;
border-radius: 15px;
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
}
- Dimensions: Centers the container and limits its width to 500px.
- Background: White (
#ffffff
) for contrast against the body. - Styling: Rounded corners (
border-radius: 15px
) and a shadow for depth.
3. Typography
h1 {
text-align: center;
color: #825CFF;
}
- Alignment: Centers headings.
- Color: Purple (
#825CFF
) for a vibrant appearance.
h1, h2, h3, p {
margin: 0;
}
- Removes default margins for consistent spacing.
4. Balance Section
.balance {
text-align: center;
margin: 20px 0;
font-weight: bold;
}
.balance p {
font-size: 24px;
color: #825CFF;
}
- Balance Display: Highlights the total balance with bold text and a larger font size.
5. Income and Expense Section
.income-expense {
display: flex;
justify-content: space-between;
gap: 15px;
margin: 20px 0;
}
- Layout: Uses flexbox to arrange income and expense boxes side by side with equal spacing.
.income-expense div {
flex: 1;
padding: 15px;
text-align: center;
background: #f8f8f8;
border-radius: 10px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
}
- Individual Boxes: Styled with light gray background, rounded corners, and a shadow.
.income-expense div.expense {
background: #ffecec;
}
- Expense Highlight: Expenses have a light red background for visual distinction.
6. Form Styling
.form-control {
display: flex;
flex-direction: column;
margin: 10px 0;
}
- Layout: Stacks form elements vertically.
.form-control input {
padding: 10px;
border: 1px solid #ddd;
border-radius: 8px;
font-size: 14px;
}
- Input Fields: Styled for readability with padding, rounded borders, and a neutral border color.
.form-control input:focus {
border-color: #825CFF;
outline: none;
}
- Focus Effect: Changes border color to purple when the user interacts with an input field.
7. Buttons
.btn {
background: #825CFF;
color: white;
padding: 10px;
border: none;
border-radius: 8px;
cursor: pointer;
transition: background 0.3s ease;
}
- Primary Button: Purple background with white text, rounded corners, and a hover effect.
.btn:hover {
background: #6e4dcb;
}
- Hover Effect: Darkens the background for interactivity.
8. History Section
.history {
margin-top: 20px;
}
- Adds spacing above the transaction history table.
9. Table Styling
table {
width: 100%;
border-collapse: collapse;
background: #ffffff;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
- Structure: Styled for a clean, minimalistic look with rounded corners and a shadow.
table th, table td {
padding: 10px;
text-align: left;
border-bottom: 1px solid #ddd;
}
- Cells: Spaced with padding and separated by a light gray border.
table td.amount {
font-weight: bold;
color: #4caf50;
}
table td.amount.expense {
color: #f44336;
}
- Highlight Amounts: Positive amounts are green, and negative amounts are red for easy identification.
10. Media Queries
@media (max-width: 768px) {
.container {
max-width: 90%;
padding: 15px;
}
.income-expense {
flex-direction: column;
gap: 10px;
}
}
- Responsive Design: Adjusts layout and spacing for smaller screens to ensure usability.
body {
font-family: 'Poppins', sans-serif;
margin: 0;
padding: 0;
background: #f0f4f8;
color: #333;
}
.container {
max-width: 500px;
margin: 30px auto;
background: #ffffff;
padding: 20px;
border-radius: 15px;
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
}
h1, h2, h3, p{
margin: 0;
}
h1 {
text-align: center;
color: #825CFF;
}
.balance {
text-align: center;
margin: 20px 0;
font-weight: bold;
}
.balance p {
font-size: 24px;
color: #825CFF;
}
.income-expense {
display: flex;
justify-content: space-between;
gap: 15px;
margin: 20px 0;
}
.income-expense div {
flex: 1;
padding: 15px;
text-align: center;
background: #f8f8f8;
border-radius: 10px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
}
.income-expense div.expense {
background: #ffecec;
}
.income-expense h3 {
margin: 0;
font-size: 18px;
color: #555;
}
.income-expense p {
font-size: 20px;
font-weight: bold;
margin: 10px 0 0;
color: #333;
}
.form-control {
display: flex;
flex-direction: column;
margin: 10px 0;
}
.form-control label {
margin-bottom: 5px;
font-weight: bold;
font-size: 14px;
}
.form-control input {
font-family: 'Poppins', sans-serif;
padding: 10px;
border: 1px solid #ddd;
border-radius: 8px;
font-size: 14px;
}
.form-control input:focus {
border-color: #825CFF;
outline: none;
}
.btn {
font-family: 'Poppins', sans-serif;
background: #825CFF;
color: white;
padding: 10px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
margin-top: 10px;
transition: background 0.3s ease;
}
.btn:hover {
background: #6e4dcb;
}
.history {
margin-top: 20px;
}
.history h3 {
margin-bottom: 10px;
font-size: 20px;
color: #825CFF;
}
table {
width: 100%;
border-collapse: collapse;
background: #ffffff;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
table th, table td {
padding: 10px;
text-align: left;
border-bottom: 1px solid #ddd;
}
table th {
background: #f0f4f8;
font-size: 16px;
color: #333;
}
table tr:hover {
background: #f9f9f9;
}
table td.amount {
font-weight: bold;
color: #4caf50;
}
table td.amount.expense {
color: #f44336;
}
table button {
background: #f44336;
color: white;
padding: 5px 8px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 12px;
transition: background 0.3s ease;
}
table button:hover {
background: #d32f2f;
}
@media (max-width: 768px) {
.container {
max-width: 90%;
padding: 15px;
}
.income-expense {
flex-direction: column;
gap: 10px;
}
.form-control input {
font-size: 12px;
}
.btn {
font-size: 12px;
padding: 8px;
}
table th, table td {
padding: 8px;
font-size: 14px;
}
}
Step 3 (JavaScript Code):
Finally, we need to create a function in JavaScript. It handles tasks like adding, removing, and displaying transactions, updating balances, and persisting data in the browser's local storage. Here's a breakdown of each part of the code:
1. Variables and Initial Setup
const balance = document.getElementById('balance');
const income = document.getElementById('income');
const expense = document.getElementById('expense');
const list = document.getElementById('list');
const text = document.getElementById('text');
const amount = document.getElementById('amount');
const addTransaction = document.getElementById('add-transaction');
let transactions = JSON.parse(localStorage.getItem('transactions')) || [];
- Element Selection: The
document.getElementById
method selects various elements by their IDs from the HTML. These variables are used to interact with the DOM. - Transactions Array:
- The
transactions
array stores all transaction objects. - It is initialized by fetching data from
localStorage
(if any). If no data exists, it defaults to an empty array.
- The
2. updateValues
Function
function updateValues() {
const amounts = transactions.map(t => t.amount);
const total = amounts.reduce((acc, item) => acc + item, 0).toFixed(2);
const incomeTotal = amounts.filter(a => a > 0).reduce((acc, item) => acc + item, 0).toFixed(2);
const expenseTotal = (amounts.filter(a => a < 0).reduce((acc, item) => acc + item, 0) * -1).toFixed(2);
balance.textContent = `$${total}`;
income.textContent = `$${incomeTotal}`;
expense.textContent = `$${expenseTotal}`;
}
- Purpose: Updates the balance, total income, and total expenses displayed on the page.
- Steps:
- Extracts the
amount
values from all transactions. - Calculates:
- Total Balance: Sum of all amounts.
- Total Income: Sum of positive amounts.
- Total Expenses: Sum of negative amounts, converted to a positive value.
- Updates the corresponding DOM elements (
balance
,income
,expense
) with the calculated values.
- Extracts the
3. addTransactionDOM
Function
function addTransactionDOM(transaction) {
const tr = document.createElement('tr');
tr.innerHTML = `
<td>${transaction.date}</td>
<td>${transaction.text}</td>
<td class="amount ${transaction.amount < 0 ? 'expense' : ''}">$${transaction.amount.toFixed(2)}</td>
<td><button onclick="removeTransaction(${transaction.id})">X</button></td>
`;
list.appendChild(tr);
}
- Purpose: Adds a new transaction row to the transaction history table.
- Steps:
- Creates a new
<tr>
(table row) element. - Populates the row with:
- Transaction date.
- Description.
- Amount (styled as an expense if negative).
- A button to remove the transaction.
- Appends the row to the transaction list (
list
).
- Creates a new
4. removeTransaction
Function
function removeTransaction(id) {
transactions = transactions.filter(t => t.id !== id);
updateLocalStorage();
updateUI();
}
- Purpose: Removes a transaction from the list.
- Steps:
- Filters out the transaction with the specified
id
. - Updates
localStorage
to reflect the change. - Calls
updateUI
to refresh the display.
- Filters out the transaction with the specified
5. updateUI
Function
function updateUI() {
list.innerHTML = '';
transactions.forEach(addTransactionDOM);
updateValues();
}
- Purpose: Updates the entire UI whenever there’s a change.
- Steps:
- Clears the transaction list (
list
). - Loops through the
transactions
array and adds each transaction to the DOM usingaddTransactionDOM
. - Updates balance, income, and expense values by calling
updateValues
.
- Clears the transaction list (
6. updateLocalStorage
Function
function updateLocalStorage() {
localStorage.setItem('transactions', JSON.stringify(transactions));
}
- Purpose: Saves the
transactions
array to the browser's local storage in JSON format.
7. Add Transaction Event Listener
addTransaction.addEventListener('click', () => {
const textValue = text.value.trim();
const amountValue = parseFloat(amount.value);
if (!textValue || isNaN(amountValue)) {
alert('Please enter valid description and amount.');
return;
}
const transaction = {
id: Date.now(),
text: textValue,
amount: amountValue,
date: new Date().toISOString().split('T')[0],
};
transactions.push(transaction);
updateLocalStorage();
updateUI();
text.value = '';
amount.value = '';
});
- Purpose: Handles the addition of a new transaction when the "Add Transaction" button is clicked.
- Steps:
- Retrieves and validates user input (
text
andamount
). - Creates a transaction object with:
- Unique
id
(timestamp). - Description (
text
). - Amount (
amount
). - Date (current date in
YYYY-MM-DD
format).
- Unique
- Adds the transaction to the
transactions
array. - Updates
localStorage
and the UI. - Clears the input fields.
- Retrieves and validates user input (
8. Initialization (init
Function)
function init() {
updateUI();
}
init();
- Purpose: Initializes the application by calling
updateUI
to load transactions fromlocalStorage
and update the display.
const balance = document.getElementById('balance');
const income = document.getElementById('income');
const expense = document.getElementById('expense');
const list = document.getElementById('list');
const text = document.getElementById('text');
const amount = document.getElementById('amount');
const addTransaction = document.getElementById('add-transaction');
let transactions = JSON.parse(localStorage.getItem('transactions')) || [];
function updateValues() {
const amounts = transactions.map(t => t.amount);
const total = amounts.reduce((acc, item) => acc + item, 0).toFixed(2);
const incomeTotal = amounts.filter(a => a > 0).reduce((acc, item) => acc + item, 0).toFixed(2);
const expenseTotal = (amounts.filter(a => a < 0).reduce((acc, item) => acc + item, 0) * -1).toFixed(2);
balance.textContent = `$${total}`;
income.textContent = `$${incomeTotal}`;
expense.textContent = `$${expenseTotal}`;
}
function addTransactionDOM(transaction) {
const tr = document.createElement('tr');
tr.innerHTML = `
<td>${transaction.date}</td>
<td>${transaction.text}</td>
<td class="amount ${transaction.amount < 0 ? 'expense' : ''}">$${transaction.amount.toFixed(2)}</td>
<td><button onclick="removeTransaction(${transaction.id})">X</button></td>
`;
list.appendChild(tr);
}
function removeTransaction(id) {
transactions = transactions.filter(t => t.id !== id);
updateLocalStorage();
updateUI();
}
function updateUI() {
list.innerHTML = '';
transactions.forEach(addTransactionDOM);
updateValues();
}
function updateLocalStorage() {
localStorage.setItem('transactions', JSON.stringify(transactions));
}
addTransaction.addEventListener('click', () => {
const textValue = text.value.trim();
const amountValue = parseFloat(amount.value);
if (!textValue || isNaN(amountValue)) {
alert('Please enter valid description and amount.');
return;
}
const transaction = {
id: Date.now(),
text: textValue,
amount: amountValue,
date: new Date().toISOString().split('T')[0],
};
transactions.push(transaction);
updateLocalStorage();
updateUI();
text.value = '';
amount.value = '';
});
function init() {
updateUI();
}
init();
Final Output:
Conclusion:
Creating a modern expense tracker using HTML, CSS, and JavaScript is a great way to improve your web development skills. This project not only helps you manage your budget but also allows you to learn how to work with arrays, DOM manipulation, and local storage.
Start building your tracker today and take control of your finances!
That’s a wrap!
I hope you enjoyed this post. Now, with these examples, you can create your own amazing page.
Did you like it? Let me know in the comments below 🔥 and you can support me by buying me a coffee
And don’t forget to sign up to our email newsletter so you can get useful content like this sent right to your inbox!
Thanks!
Faraz 😊