Beginner1 hourHTMLCSSJavaScript

Build a Tip Calculator with JavaScript

Split a bill between friends with live tip buttons. Your first real interactive app.

What you'll build

You'll build a tip calculator that updates as you type: enter the bill, choose a tip percentage, set the number of people, and instantly see the tip and how much each person pays.

It's small, but it uses the full front-end toolkit: a form in HTML, a layout in CSS, and JavaScript that reads inputs, listens for events, does maths and updates the page.

How the calculator thinks

Before writing JavaScript, it helps to say out loud what the app does. Every time something changes, the calculator follows the same four steps:

  1. Read the bill, the tip percentage and the number of people.
  2. Check the values. An empty box or zero people would break the maths.
  3. Calculate the tip, the total and each person's share.
  4. Show the results on the page.

That loop of read, check, calculate and show is the heart of almost every interactive web app, from a mortgage calculator to a shopping basket. Once you've built it here, you'll recognise it everywhere.

The maths itself is short:

  • tip = bill × tip percentage ÷ 100
  • total = bill + tip
  • per person = total ÷ people

The interesting part is handling real input, because people type unexpected things.

Why input values need converting

Anything you read from an <input> with .value is a string, even when the input type is number. If you add two strings in JavaScript, they are joined together rather than added: "20" + "5" gives "205". That's why the calculator turns each value into a number with Number() or parseFloat() before doing any maths.

If the box is empty or contains something that isn't a number, the conversion gives NaN ("not a number"). The JavaScript numbers lesson shows how to test for it with Number.isNaN(), and the calculator uses that check to show £0.00 instead of a broken result.

To display money, toFixed(2) rounds to two decimal places and returns a string such as "12.50", which is exactly what a receipt shows.

One function for every change

There are three inputs and three tip buttons, and any of them can change at any moment. A beginner's first instinct is often to write separate code for each one: one function for when the bill changes, another for the people field, another for each button. That quickly turns into six copies of the same maths, and fixing a bug means fixing it six times.

The calculator avoids this with a single calculate() function that always reads every value and recalculates everything. Each event listener does just one thing: call calculate(). The tip buttons store their percentage in a variable first, then call the same function. Because the maths only lives in one place, it's easy to test, easy to change, and impossible for the results to disagree with each other. The functions lesson explains why small, reusable functions like this make programs easier to grow.

Requirements checklist

  • A labelled input for the bill amount
  • Tip buttons for 10%, 15% and 20%, with the selected one highlighted
  • A labelled input for the number of people (minimum 1)
  • Results for tip total, grand total and amount per person, formatted to 2 decimals
  • Results update instantly whenever anything changes
  • No NaN or Infinity is ever shown; invalid input shows £0.00

Starter code

Press Run to see where you're starting from, then follow the steps.

// Your JavaScript here
ResultEdit the code, then press Run or Ctrl+Enter

Step-by-step guide

  1. 1

    Select the elements

    Grab everything you'll read or update, and keep the selected tip in a variable.

    JavaScript
    const billInput = document.querySelector("#bill");
    const peopleInput = document.querySelector("#people");
    const tipButtons = document.querySelectorAll("[data-tip]");
    const tipOut = document.querySelector("#tipOut");
    const totalOut = document.querySelector("#totalOut");
    const eachOut = document.querySelector("#eachOut");
    
    let tipPercent = 15;
  2. 2

    Write the calculation function

    One function reads the inputs, does the maths and updates the page. Guard against bad input so you never show NaN.

    JavaScript
    const money = (n) => "£" + n.toFixed(2);
    
    function update() {
      const bill = Number(billInput.value) || 0;
      const people = Math.max(1, Number(peopleInput.value) || 1);
    
      const tip = bill * tipPercent / 100;
      const total = bill + tip;
    
      tipOut.textContent = money(tip);
      totalOut.textContent = money(total);
      eachOut.textContent = money(total / people);
    }

    || 0 turns an empty input (which becomes 0 or NaN) into 0.

  3. 3

    Listen for typing

    The input event fires on every keystroke.

    JavaScript
    billInput.addEventListener("input", update);
    peopleInput.addEventListener("input", update);
  4. 4

    Make the tip buttons work

    Read each button's data-tip, highlight the chosen one and recalculate.

    JavaScript
    tipButtons.forEach((button) => {
      button.addEventListener("click", () => {
        tipPercent = Number(button.dataset.tip);
        tipButtons.forEach((b) => b.classList.toggle("active", b === button));
        update();
      });
    });
    
    update();

    classList.toggle("active", condition) adds the class when the condition is true and removes it otherwise.

  5. 5

    Style it

    CSS
    .calc {
      max-width: 320px;
      margin: 20px auto;
      padding: 24px;
      font-family: system-ui, sans-serif;
      background: #fff;
      border: 2px solid #15161b;
      border-radius: 20px;
      box-shadow: 6px 6px 0 #15161b;
    }
    
    label { display: grid; gap: 4px; margin: 12px 0; font-weight: 600; }
    input { padding: 10px; font-size: 1.1rem; border: 2px solid #ccc; border-radius: 10px; }
    
    .tips { display: flex; gap: 8px; }
    .tips button {
      flex: 1; padding: 10px; font-weight: 700;
      border: 2px solid #15161b; border-radius: 10px; background: #fff; cursor: pointer;
    }
    .tips button.active { background: #17a865; color: white; }
    
    .results { margin-top: 16px; padding: 12px 16px; background: #15161b; color: white; border-radius: 14px; }
    .results strong { float: right; color: #ffd23f; }

Final solution

Show the complete solution
const billInput = document.querySelector("#bill");
const peopleInput = document.querySelector("#people");
const tipButtons = document.querySelectorAll("[data-tip]");
const tipOut = document.querySelector("#tipOut");
const totalOut = document.querySelector("#totalOut");
const eachOut = document.querySelector("#eachOut");

let tipPercent = 15;

const money = (n) => "£" + n.toFixed(2);

function update() {
  const bill = Number(billInput.value) || 0;
  const people = Math.max(1, Number(peopleInput.value) || 1);

  const tip = bill * tipPercent / 100;
  const total = bill + tip;

  tipOut.textContent = money(tip);
  totalOut.textContent = money(total);
  eachOut.textContent = money(total / people);
}

billInput.addEventListener("input", update);
peopleInput.addEventListener("input", update);

tipButtons.forEach((button) => {
  button.addEventListener("click", () => {
    tipPercent = Number(button.dataset.tip);
    tipButtons.forEach((b) => b.classList.toggle("active", b === button));
    update();
  });
});

update();
ResultEdit the code, then press Run or Ctrl+Enter

Bonus challenges

  • Add a "Custom %" input that overrides the buttons.
  • Round the per-person amount up to the nearest 10p with Math.ceil so nobody's short.
  • Remember the last tip choice with localStorage.

Common mistakes and how to fix them

The total shows 205 instead of 25. You added strings. Convert inputs with Number(input.value) before adding.

The page shows NaN or Infinity. NaN appears when an input is empty. Infinity appears when you divide by zero people. Check both before calculating and return early with zero values.

Results only update when you click a button. Listen for the input event on each field, not change. The input event fires on every keystroke, so results update as you type. The events lesson lists the most useful event types.

Two tip buttons look selected at once. When a button is clicked, remove the active class from all buttons first, then add it to the clicked one.

Check your work

Test the calculator with awkward values, not just nice round ones:

BillTipPeopleYou should see
5010%2tip 5.00, per person 27.50
33.3315%3per person 12.78
(empty)20%40.00 everywhere
8020%00.00, never Infinity

If every row matches, your validation works. Then try it on a phone: inputmode="decimal" on the bill field brings up a number keyboard, which makes a big difference to how the app feels.

Questions beginners ask

Why use a form if nothing is submitted? Labels, inputs and the keyboard behaviour of forms all come for free, and the HTML forms lesson explains why labelled inputs matter for accessibility.

Should money be calculated with decimals? For a tip calculator, rounding the final result with toFixed(2) is fine. Banking software stores money as whole pennies to avoid tiny rounding errors, which is a good habit to learn later.

What should I build next? The to-do list app adds saving data and working with lists, the next big step after reading inputs.