What You'll Build

By the end of this tutorial, you'll have a working BMI calculator — a real, usable tool that:

  • Accepts height (in cm) and weight (in kg) from the user
  • Calculates BMI using the formula: weight ÷ (height in meters)²
  • Displays the BMI number and its category (Underweight, Normal, Overweight, Obese)
  • Colour-codes the result based on the category
  • Updates instantly as you type — no submit button needed
  • Handles invalid input gracefully
▶ Live Preview — this is what you'll build

What You'll Learn

Before you start: This tutorial assumes you know basic HTML tags and can write a JavaScript function. If you're brand new, take 20 minutes through our HTML course first — you'll get more from this project.

1

Set Up the HTML Structure

Create a new file called index.html. This is the skeleton — a card containing two input fields and a result area.

HTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>BMI Calculator</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="card">
        <h1>⚖️ BMI Calculator</h1>
        <p class="sub">Enter your height and weight</p>

        <div class="field">
            <label for="height">Height (cm)</label>
            <input type="number" id="height" placeholder="170">
        </div>

        <div class="field">
            <label for="weight">Weight (kg)</label>
            <input type="number" id="weight" placeholder="70">
        </div>

        <div class="result" id="result">
            <div class="num" id="num">—</div>
            <div class="cat" id="cat">Enter values above</div>
        </div>
    </div>

    <script src="script.js"></script>
</body>
</html>

Why this structure? Each element has a purpose:

2

Style It with CSS

Create a new file called style.css in the same folder. This makes the calculator look clean and modern.

CSS
/* Reset defaults so spacing is predictable */
* { box-sizing: border-box; margin: 0; padding: 0; }

/* Center the card on the page */
body {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    background: #f1f5f9;
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    padding: 20px;
    color: #0f172a;
}

/* The white card */
.card {
    background: #ffffff;
    border-radius: 16px;
    padding: 28px;
    max-width: 380px;
    width: 100%;
    box-shadow: 0 10px 40px rgba(15, 23, 42, 0.1);
}

h1 { font-size: 1.4rem; margin-bottom: 4px; }
.sub { color: #64748b; font-size: 0.85rem; margin-bottom: 20px; }

.field { margin-bottom: 14px; }

label {
    display: block;
    font-size: 0.82rem;
    font-weight: 600;
    color: #334155;
    margin-bottom: 6px;
}

input {
    width: 100%;
    padding: 12px 14px;
    border: 2px solid #e2e8f0;
    border-radius: 10px;
    font-size: 1rem;
    font-weight: 600;
    outline: none;
    transition: 0.2s;
}

input:focus {
    border-color: #3b82f6;
    box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
}

/* Result box — default neutral state */
.result {
    margin-top: 20px;
    padding: 20px;
    border-radius: 12px;
    text-align: center;
    background: #f1f5f9;
    transition: 0.3s;
}

.result .num {
    font-size: 2.4rem;
    font-weight: 900;
    line-height: 1;
    color: #64748b;
    font-family: "Courier New", monospace;
}

.result .cat {
    font-size: 0.9rem;
    font-weight: 700;
    margin-top: 6px;
    color: #64748b;
}

/* Category colors */
.result.under  { background: #e0f2fe; }
.result.under .num, .result.under .cat { color: #0284c7; }

.result.normal { background: #dcfce7; }
.result.normal .num, .result.normal .cat { color: #16a34a; }

.result.over   { background: #fef3c7; }
.result.over .num, .result.over .cat { color: #d97706; }

.result.obese  { background: #fee2e2; }
.result.obese .num, .result.obese .cat { color: #dc2626; }

Key CSS concept: We define four result states (.under, .normal, .over, .obese) and JavaScript will add the right class to #result. This is a common pattern — CSS handles the visual states, JS decides which state to show.

3

Write the JavaScript Logic

Create script.js in the same folder. This is where the calculator actually does its job.

JavaScript
// 1. Grab the elements from the page
const heightEl = document.getElementById("height");
const weightEl = document.getElementById("weight");
const resultEl = document.getElementById("result");
const numEl    = document.getElementById("num");
const catEl    = document.getElementById("cat");

// 2. The main function — runs every time an input changes
function calculate() {
    const h = parseFloat(heightEl.value);
    const w = parseFloat(weightEl.value);

    // 3. Guard clause — if input is invalid, show placeholder
    if (!h || !w || h <= 0 || w <= 0) {
        resultEl.className = "result";
        numEl.textContent = "—";
        catEl.textContent = "Enter values above";
        return;
    }

    // 4. BMI formula — height must be in meters
    const meters = h / 100;
    const bmi = w / (meters * meters);
    const rounded = bmi.toFixed(1);

    // 5. Determine category and CSS class
    let cat, cls;
    if (bmi < 18.5) {
        cat = "Underweight";   cls = "under";
    } else if (bmi < 25) {
        cat = "Normal weight"; cls = "normal";
    } else if (bmi < 30) {
        cat = "Overweight";    cls = "over";
    } else {
        cat = "Obese";         cls = "obese";
    }

    // 6. Update the page
    resultEl.className = "result " + cls;
    numEl.textContent = rounded;
    catEl.textContent = cat;
}

// 7. Recalculate every time the user types
heightEl.addEventListener("input", calculate);
weightEl.addEventListener("input", calculate);

// 8. Run once on load in case the fields are pre-filled
calculate();

Save all three files and open index.html in your browser. Type a height and weight — the BMI should calculate instantly. You just built a working calculator.

4

Understand What You Just Built

Take a moment to appreciate what's happening here. You used the input → calculate → display pattern — the backbone of every calculator app, from a simple tip splitter to a complex mortgage simulator.

Here's the pattern abstracted:

  1. Capture input: parseFloat() converts the text inside the input into a usable number.
  2. Validate: The if (!h || !w) guard prevents NaN errors and division by zero.
  3. Process: The formula runs — in this case, w / (h/100)².
  4. Display: We update two DOM elements: the number and the category.
  5. State styling: By swapping the class on #result, CSS handles all visual feedback.
  6. Live updates: The input event fires on every keystroke — no button needed.
5

Extend It — Practice Challenges

You have a working calculator. Now it's time to make it your own. Try these challenges in order — each one builds on what you've learned.

🟢 Beginner Challenges

🟡 Intermediate Challenges

🔴 Advanced Challenges

Compare your solution: We built a full production version with all the advanced features. See how we handled the scale bar, imperial toggle, and BMI Prime — open the live BMI calculator.

Full Source Code

All three files together. Copy them into a folder and open index.html.

Common Mistakes & Fixes

❌ "Nothing happens when I type"

Check that you attached the event listeners correctly. Open the browser console (F12) — if you see an error like "Cannot read property 'value' of null," your element IDs don't match. Verify id="height" in HTML matches getElementById("height") in JS.

❌ "My result shows NaN"

NaN means "Not a Number." You're probably trying to calculate with empty inputs. The guard clause at the top of calculate() should catch this — check that it's not been accidentally removed.

❌ "Division by zero gives Infinity"

If height is 0, dividing weight by it gives Infinity. The guard clause (h <= 0) prevents this.

❌ "My category colours don't change"

Check the CSS class names — .result.under, .result.normal, etc. — match exactly what JavaScript sets. CSS class names are case-sensitive.

What to Build Next

You've learned the input → calculate → display pattern. Here are the natural next projects that use the same skill set with new twists:

Learn More — Related Lessons

This project used concepts from these lessons. If any part felt shaky, review them:

Snippets Used in This Project

Grab these ready-made snippets if you want to skip the copy-paste and start from a building block: