Build a Shopping Cart with Vanilla JavaScript
Build a shopping cart with plain JavaScript that lets a user add products, change quantities, remove items, and see a running total.
A JavaScript shopping cart lets a user add products, adjust how many of each they want, remove items, and see an accurate total price update as they go. It extends the array-and-render pattern from a todo list with a detail that matters for real commerce: the same product should never appear twice, it should just have its quantity increased.
You will build a small product list with add-to-cart buttons, a cart display that shows the quantity of each item, and a total that recalculates automatically as the cart changes.
What You Will Build
The cart needs to support:
- Adding a product to the cart, or increasing its quantity if it is already there.
- Increasing a product's quantity by adding it to the cart again.
- Removing a product from the cart.
- Showing an accurate total price at all times.
Every change to the cart follows the same shape as the earlier mini projects: update the array first, then let the page catch up. The one new piece is the total, which is recalculated from the array instead of tracked separately.
Step 1: Build the HTML
Start with a small product list, an empty cart container, and a place to show the total.
<div id="products">
<button data-id="1" data-name="Keyboard" data-price="49.99">Add Keyboard</button>
<button data-id="2" data-name="Mouse" data-price="19.99">Add Mouse</button>
</div>
<div id="cartList"></div>
<p id="cartTotal">Total: $0.00</p>Each product button carries its id, name, and price directly in data attributes, the same pattern used for the calculator's buttons. The cart list and total start empty, since JavaScript builds both from the cart array.
Step 2: Store the Cart as an Array of Objects
Before writing the cart logic, select the elements and set up the array that will hold it.
const products = document.getElementById("products");
const cartList = document.getElementById("cartList");
const cartTotal = document.getElementById("cartTotal");
let cart = [];Each cart item will be an object with a product id, name, price, and quantity. Keeping price and quantity as separate fields, rather than one combined line total, lets the cart recalculate correctly whenever the quantity changes.
Step 3: Add a Product to the Cart
Adding a product needs to check whether it is already in the cart, not just push a new entry every time.
function addToCart(product) {
const existingItem = cart.find((item) => item.id === product.id);
if (existingItem) {
existingItem.quantity += 1;
} else {
cart.push({ ...product, quantity: 1 });
}
renderCart();
}The function looks for a cart item with a matching id first. If one exists, it increases that item's quantity instead of adding a duplicate row. If not, it adds the product to the cart with a starting quantity of one. This search relies on the same lookup used in the Array find and findIndex guide.
Wire the product buttons to this function with one delegated listener, the same event delegation pattern used in the calculator project:
products.addEventListener("click", (event) => {
const button = event.target;
if (button.tagName !== "BUTTON") return;
addToCart({
id: Number(button.dataset.id),
name: button.dataset.name,
price: Number(button.dataset.price),
});
});Clicking a product button reads its id, name, and price from its data attributes and passes them to addToCart as a plain object. Because the product data comes from attributes you wrote yourself, not from user text input, building the cart rows from it in the next step is safe even where the markup uses template strings.
Step 4: Render the Cart
Write one function that rebuilds the cart display from the array, including a way to change or remove each item.
function renderCart() {
cartList.innerHTML = "";
cart.forEach((item) => {
const row = document.createElement("div");
row.innerHTML = `
<span>${item.name} x ${item.quantity}</span>
<button data-id="${item.id}" data-action="remove">Remove</button>
`;
cartList.appendChild(row);
});
updateTotal();
}This function clears the cart display and creates one row per item, showing the name and quantity together with a remove button. It calls updateTotal at the end so the price on screen always matches what is actually in the cart.
Step 5: Calculate the Total
The total should never be tracked as its own separate variable that gets manually adjusted. Instead, calculate it fresh from the cart array every time.
function updateTotal() {
const total = cart.reduce((sum, item) => sum + item.price * item.quantity, 0);
cartTotal.textContent = `Total: $${total.toFixed(2)}`;
}reduce walks through every cart item and adds each item's price multiplied by its quantity to a running sum, starting from zero. Calculating the total this way means it can never drift out of sync with the cart, since it is always built from the current array rather than adjusted by hand. See the Array reduce method guide for a closer look at how this method works.
Step 6: Remove an Item
Removing an item uses one listener on the cart container, the same event delegation pattern used in the earlier mini projects.
cartList.addEventListener("click", (event) => {
const id = Number(event.target.dataset.id);
if (event.target.dataset.action === "remove") {
cart = cart.filter((item) => item.id !== id);
renderCart();
}
});Clicking a remove button filters that item out of the cart array by id, then re-renders. Because renderCart already calls updateTotal, the price on screen updates automatically without any extra code here.
Common Mistakes
| Mistake | Why it breaks | Fix |
|---|---|---|
| Adding a duplicate row for a product already in the cart | The same product appears twice instead of as one item with a higher quantity | Check for an existing item with Array.find before adding a new one |
| Tracking the total in a separate variable updated by hand | The total drifts out of sync after a few adds, edits, or removals | Recalculate the total from the cart array every time it changes |
| Matching cart items by name instead of id | Two different products with the same name get merged incorrectly | Always match cart items using a stable, unique id |
Next Step
You have now built the full set of array, render, and calculate patterns used across most small data-driven apps. If you have not built the JavaScript todo list app yet, it is a simpler starting point for the same array-and-render approach used here.
Rune AI
Key Insights
- Store the cart as an array of objects, each with a product id, name, price, and quantity.
- Check whether a product is already in the cart before adding it, and increase its quantity instead of duplicating it.
- Recalculate the cart total from the array every time it changes, rather than tracking a separate total variable by hand.
- Use Array.reduce to turn the array of cart items into a single total in one line.
- Re-render the whole cart from the array after every change so the page and the data never drift apart.
Frequently Asked Questions
Why check if a product is already in the cart before adding it?
Why store product price and quantity separately instead of one combined total per item?
Is Array.reduce necessary for calculating the cart total?
Conclusion
A shopping cart brings together everything from the earlier mini projects: an array of objects as the source of truth, a render function that rebuilds the page from that array, and a calculation that summarizes the whole array into one number. That combination is the foundation of most real e-commerce and order-management interfaces.
More in this topic
Using Reflect and Proxy Together in JavaScript
Proxy traps intercept operations, but Reflect is what makes them behave correctly. Learn why Reflect belongs inside every trap and what breaks when you skip it.
Top JS Array Methods Interview Questions to Know
The array method questions that come up most often in JavaScript interviews, answered directly with short examples: map vs forEach, mutating vs non-mutating methods, reduce, and more.
JavaScript Reflect API: Advanced Architecture
The Reflect object exposes JavaScript's own internal operations as plain functions. Learn every Reflect method, what it returns, and why it exists as its own API.