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.

8 min read

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.
Shopping cart update flow

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.

htmlhtml
<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.

javascriptjavascript
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.

javascriptjavascript
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:

javascriptjavascript
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.

javascriptjavascript
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.

javascriptjavascript
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.

javascriptjavascript
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

MistakeWhy it breaksFix
Adding a duplicate row for a product already in the cartThe same product appears twice instead of as one item with a higher quantityCheck for an existing item with Array.find before adding a new one
Tracking the total in a separate variable updated by handThe total drifts out of sync after a few adds, edits, or removalsRecalculate the total from the cart array every time it changes
Matching cart items by name instead of idTwo different products with the same name get merged incorrectlyAlways 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

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.
RunePowered by Rune AI

Frequently Asked Questions

Why check if a product is already in the cart before adding it?

Without that check, adding the same product twice creates two separate line items instead of increasing the quantity of one, which does not match how a real shopping cart behaves.

Why store product price and quantity separately instead of one combined total per item?

Keeping price and quantity apart lets the cart recalculate a line total whenever the quantity changes, without needing to know or re-derive the original price from anywhere else.

Is Array.reduce necessary for calculating the cart total?

No, a plain loop works too, but reduce expresses the same calculation in one line and is a common pattern for turning an array of items into a single summary value.

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.