Chapter 7: Build Frontend Projects

Theory is important, but building projects is where you truly learn. This chapter provides a list of 10 classic frontend projects you can build using only **HTML, CSS, and JavaScript**. Each project will help you solidify different skills, from DOM manipulation and handling events to fetching data from APIs and storing information in the browser.

For each project, we'll explain the core logic and provide a complete, runnable code snippet for a basic version. You can copy this code, save it as an `.html` file, and see it work instantly. Your challenge is to then expand and improve upon it!

1. To-Do List App

A classic beginner project. This app allows users to add tasks to a list, mark them as complete, and delete them.

  • Skills Practiced: DOM Manipulation (creating, adding, deleting elements), Event Handling (`click`, `submit`), basic state management in the UI.

Core Logic:

  1. Listen for the form submission to get the new task text.
  2. Create a new list item (`
  3. `) element in JavaScript.
  4. Add the task text and a "Delete" button inside the `
  5. `.
  6. Append the new `
  7. ` to the main task list (`
      `).
    • Add event listeners to the `
    • ` (for toggling complete) and the "Delete" button.

Example Code:

<!DOCTYPE html>
<html><head><title>To-Do List</title>
<style>
    body { font-family: sans-serif; background: #222; color: #eee; text-align: center; }
    .container { max-width: 500px; margin: 20px auto; padding: 20px; background: #333; border-radius: 8px; }
    input { width: 70%; padding: 8px; } button { padding: 8px; } ul { list-style-type: none; padding: 0; text-align: left;}
    li { padding: 10px; border-bottom: 1px solid #444; display: flex; justify-content: space-between; align-items: center; cursor: pointer; }
    .completed { text-decoration: line-through; color: #888; }
    .delete-btn { background: #e74c3c; color: white; border: none; cursor: pointer; padding: 5px 10px; border-radius: 4px; }
</style></head><body>
<div class="container">
    <h1>My To-Do List</h1>
    <form id="todo-form">
        <input type="text" id="todo-input" placeholder="Add a new task..." required>
        <button type="submit">Add Task</button>
    </form>
    <ul id="todo-list"></ul>
</div>
<script>
    const form = document.getElementById('todo-form');
    const input = document.getElementById('todo-input');
    const list = document.getElementById('todo-list');
    form.addEventListener('submit', function(e) {
        e.preventDefault();
        const li = document.createElement('li');
        const taskSpan = document.createElement('span');
        taskSpan.textContent = input.value;
        const deleteBtn = document.createElement('button');
        deleteBtn.textContent = 'Delete';
        deleteBtn.classList.add('delete-btn');
        li.appendChild(taskSpan);
        li.appendChild(deleteBtn);
        list.appendChild(li);
        input.value = '';
        li.addEventListener('click', (event) => {
            if (event.target.tagName !== 'BUTTON') { li.classList.toggle('completed'); }
        });
        deleteBtn.addEventListener('click', () => { list.removeChild(li); });
    });
</script></body></html>

2. Weather App

An application where a user can enter a city name and get the current weather information.

  • Skills Practiced: Fetching data from a third-party API (`fetch`), `async/await`, JSON, DOM manipulation.

Core Logic:

  1. Get a free API key (e.g., from OpenWeatherMap).
  2. Get the city name from the user.
  3. Use `fetch()` and `await` to make a request to the weather API URL with the city and your key.
  4. Parse the JSON response and extract the temperature, description, and icon.
  5. Update the DOM to display the weather info and handle any errors.

Example Code:

<!DOCTYPE html>
<html><head><title>Weather App</title>
<style>
    body { font-family: sans-serif; background: #2c3e50; color: #ecf0f1; text-align: center; }
    .container { max-width: 400px; margin: 50px auto; padding: 20px; background: #34495e; border-radius: 8px; }
    input { padding: 10px; border-radius: 5px; border: none; }
    button { padding: 10px; border-radius: 5px; border: none; cursor: pointer; background: #3498db; color: white; }
    #weather-result { margin-top: 20px; }
</style></head><body>
<div class="container">
    <h1>Weather App</h1>
    <input type="text" id="city-input" placeholder="Enter city name...">
    <button id="get-weather-btn">Get Weather</button>
    <div id="weather-result"><p>Enter a city to get the weather.</p></div>
</div>
<script>
    const cityInput = document.getElementById('city-input');
    const getWeatherBtn = document.getElementById('get-weather-btn');
    const weatherResult = document.getElementById('weather-result');
    const apiKey = 'YOUR_API_KEY_HERE'; // IMPORTANT: Replace with your key
    getWeatherBtn.addEventListener('click', async () => {
        const city = cityInput.value;
        if (!city) { alert('Please enter a city name.'); return; }
        const apiUrl = `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${apiKey}&units=metric`;
        try {
            weatherResult.innerHTML = '<p>Fetching weather...</p>';
            const response = await fetch(apiUrl);
            if (!response.ok) { throw new Error('City not found.'); }
            const data = await response.json();
            weatherResult.innerHTML = `
                <h2>${data.name}</h2>
                <p>${data.main.temp}°C, ${data.weather[0].description}</p>
                <img src="https://openweathermap.org/img/wn/${data.weather[0].icon}@2x.png">
            `;
        } catch (error) {
            weatherResult.innerHTML = `<p style="color: red;">${error.message}</p>`;
        }
    });
</script></body></html>

3. Quiz App

A multiple-choice quiz that shows a final score.

  • Skills Practiced: JavaScript logic, arrays of objects, event handling, managing state (score, question index).

Core Logic:

  1. Store questions, options, and answers in a JavaScript array of objects.
  2. Display the current question and options.
  3. When a user clicks an option, check if it's correct, update the score, and provide feedback.
  4. Load the next question or show the final score screen.

Example Code:

<!DOCTYPE html>
<html><head><title>Quiz App</title>
<style>
    body { font-family: sans-serif; background: #222; color: #eee; }
    .quiz-container { max-width: 600px; margin: 50px auto; background: #333; padding: 20px; border-radius: 8px; }
    .option { list-style-type: none; margin: 10px 0; padding: 10px; background: #444; cursor: pointer; border-radius: 5px; }
    .option:hover { background: #555; }
    #feedback { margin-top: 15px; font-weight: bold; height: 20px; }
</style></head><body>
<div class="quiz-container">
    <div id="quiz-view">
        <h2 id="question"></h2>
        <ul id="options-container"></ul>
        <div id="feedback"></div>
        <button id="next-btn" style="display:none;">Next</button>
    </div>
    <div id="score-view" style="display: none;">
        <h2 id="score-text"></h2>
        <button id="restart-btn">Restart Quiz</button>
    </div>
</div>
<script>
    const questions = [
        { q: "Capital of France?", o: ["Berlin", "Paris", "Rome"], a: "Paris" },
        { q: "Language of the web?", o: ["Java", "Python", "JavaScript"], a: "JavaScript" }
    ];
    let currentQ = 0; let score = 0;
    const questionEl = document.getElementById('question');
    const optionsContainer = document.getElementById('options-container');
    const nextBtn = document.getElementById('next-btn');
    const feedbackEl = document.getElementById('feedback');
    function loadQuestion() {
        feedbackEl.textContent = ''; nextBtn.style.display = 'none';
        questionEl.textContent = questions[currentQ].q;
        optionsContainer.innerHTML = '';
        questions[currentQ].o.forEach(opt => {
            const li = document.createElement('li'); li.classList.add('option');
            li.textContent = opt;
            li.addEventListener('click', () => selectAnswer(opt, questions[currentQ].a));
            optionsContainer.appendChild(li);
        });
    }
    function selectAnswer(selected, correct) {
        if (selected === correct) { feedbackEl.textContent = "Correct!"; score++; }
        else { feedbackEl.textContent = `Wrong! Answer was ${correct}`; }
        nextBtn.style.display = 'block';
        optionsContainer.querySelectorAll('li').forEach(item => item.style.pointerEvents = 'none');
    }
    nextBtn.addEventListener('click', () => { currentQ++; (currentQ < questions.length) ? loadQuestion() : showScore(); });
    document.getElementById('restart-btn').addEventListener('click', () => { currentQ = 0; score = 0; document.getElementById('quiz-view').style.display = 'block'; document.getElementById('score-view').style.display = 'none'; loadQuestion(); });
    function showScore() { document.getElementById('quiz-view').style.display = 'none'; document.getElementById('score-view').style.display = 'block'; document.getElementById('score-text').textContent = `You scored ${score} out of ${questions.length}!`; }
    loadQuestion();
</script></body></html>

4. Note-Taking App with Local Storage

A simple app to write and save notes that persist even after the browser is closed.

  • Skills Practiced: Local Storage API, JSON `stringify`/`parse`, DOM Manipulation, arrays of objects.

Core Logic:

  1. On page load, check Local Storage for saved notes. If found, parse the JSON and display them.
  2. When a user saves a note, add it to an array.
  3. Convert the array to a JSON string with `JSON.stringify()`.
  4. Save this string to Local Storage with `localStorage.setItem()`.
  5. When deleting a note, remove it from the array and re-save the updated array to Local Storage.

Example Code:

<!DOCTYPE html>
<html><head><title>Note Taking App</title>
<style>
    body { font-family: sans-serif; background: #333; color: #eee; display: flex; gap: 20px; padding: 20px; }
    .editor, .notes-display { background: #444; padding: 20px; border-radius: 8px; width: 50%; }
    textarea { width: 100%; height: 150px; box-sizing: border-box;}
    .note { background: #555; padding: 10px; margin-bottom: 10px; border-radius: 4px; display: flex; justify-content: space-between; }
</style></head><body>
<div class="editor">
    <h2>Add a Note</h2>
    <textarea id="note-input" placeholder="Write your note..."></textarea>
    <button id="save-btn">Save Note</button>
</div>
<div class="notes-display">
    <h2>Saved Notes</h2>
    <div id="notes-container"></div>
</div>
<script>
    const noteInput = document.getElementById('note-input');
    const saveBtn = document.getElementById('save-btn');
    const notesContainer = document.getElementById('notes-container');
    const getNotes = () => JSON.parse(localStorage.getItem('notes') || '[]');
    const saveNotes = (notes) => localStorage.setItem('notes', JSON.stringify(notes));
    function displayNotes() {
        const notes = getNotes();
        notesContainer.innerHTML = '';
        notes.forEach((noteText, index) => {
            const noteDiv = document.createElement('div'); noteDiv.classList.add('note');
            const textSpan = document.createElement('span'); textSpan.textContent = noteText;
            const deleteBtn = document.createElement('button'); deleteBtn.textContent = 'X';
            deleteBtn.onclick = () => { notes.splice(index, 1); saveNotes(notes); displayNotes(); };
            noteDiv.appendChild(textSpan); noteDiv.appendChild(deleteBtn);
            notesContainer.appendChild(noteDiv);
        });
    }
    saveBtn.addEventListener('click', () => {
        const notes = getNotes();
        if (noteInput.value) {
            notes.push(noteInput.value);
            saveNotes(notes);
            noteInput.value = '';
            displayNotes();
        }
    });
    displayNotes();
</script></body></html>

5. Tip Calculator

Calculates the tip amount and total bill per person.

  • Skills Practiced: Math operations, form inputs (`input` events), real-time UI updates.

Core Logic:

  1. Get the bill amount, tip percentage, and number of people from input fields.
  2. Add an `input` event listener to all fields to trigger recalculation on any change.
  3. Inside the calculation function, parse the input values as numbers.
  4. Calculate `tipAmount = bill * (tipPercentage / 100)`.
  5. Calculate `totalBill = bill + tipAmount`.
  6. Calculate `tipPerPerson = tipAmount / numberOfPeople` and `totalPerPerson = totalBill / numberOfPeople`.
  7. Update the DOM to display the results. Handle division by zero.

Example Code:

<!DOCTYPE html>
<html><head><title>Tip Calculator</title>
<style>
    body { font-family: sans-serif; background-color: #f4f4f4; color: #333; display: grid; place-items: center; height: 100vh; margin: 0;}
    .calculator { background: white; padding: 20px; border-radius: 10px; box-shadow: 0 0 10px rgba(0,0,0,0.1); width: 300px; }
    .input-group { margin-bottom: 15px; }
    label { display: block; margin-bottom: 5px; }
    input { width: 100%; padding: 8px; box-sizing: border-box; }
    .results { margin-top: 20px; } h3 { margin: 5px 0; }
</style></head><body>
<div class="calculator">
    <h1>Tip Calculator</h1>
    <div class="input-group">
        <label for="bill">Bill Amount ($)</label>
        <input type="number" id="bill" value="0" min="0">
    </div>
    <div class="input-group">
        <label for="tip">Tip Percentage (%)</label>
        <input type="number" id="tip" value="15" min="0">
    </div>
    <div class="input-group">
        <label for="people">Number of People</label>
        <input type="number" id="people" value="1" min="1">
    </div>
    <div class="results">
        <h3>Tip per Person: $<span id="tip-per-person">0.00</span></h3>
        <h3>Total per Person: $<span id="total-per-person">0.00</span></h3>
    </div>
</div>
<script>
    const billInput = document.getElementById('bill');
    const tipInput = document.getElementById('tip');
    const peopleInput = document.getElementById('people');
    const tipPerPersonEl = document.getElementById('tip-per-person');
    const totalPerPersonEl = document.getElementById('total-per-person');
    function calculate() {
        const bill = parseFloat(billInput.value) || 0;
        const tipPercent = parseFloat(tipInput.value) || 0;
        const people = parseInt(peopleInput.value) || 1;
        if (bill >= 0 && tipPercent >= 0 && people > 0) {
            const tipAmount = bill * (tipPercent / 100);
            const totalAmount = bill + tipAmount;
            const tipPerPerson = tipAmount / people;
            const totalPerPerson = totalAmount / people;
            tipPerPersonEl.textContent = tipPerPerson.toFixed(2);
            totalPerPersonEl.textContent = totalPerPerson.toFixed(2);
        }
    }
    billInput.addEventListener('input', calculate);
    tipInput.addEventListener('input', calculate);
    peopleInput.addEventListener('input', calculate);
    calculate(); // Initial calculation
</script></body></html>

7. Simple Drawing App

A canvas where users can draw with their mouse.

  • Skills Practiced: HTML Canvas API, `mousedown`, `mousemove`, `mouseup` events.

Core Logic:

  1. Get the `` element and its 2D rendering context.
  2. Create a `drawing` flag, initially `false`.
  3. On `mousedown`, set `drawing` to `true` and get the starting coordinates.
  4. On `mousemove`, if `drawing` is `true`, draw a line from the last coordinates to the current mouse coordinates.
  5. On `mouseup` or `mouseleave`, set `drawing` to `false`.

Example Code:

<!DOCTYPE html>
<html><head><title>Drawing App</title>
<style>
    body { font-family: sans-serif; text-align: center; }
    canvas { border: 2px solid black; cursor: crosshair; }
</style></head><body>
    <h1>Simple Drawing App</h1>
    <canvas id="draw-canvas" width="600" height="400"></canvas>
    <br>
    <button id="clear-btn">Clear</button>
<script>
    const canvas = document.getElementById('draw-canvas');
    const clearBtn = document.getElementById('clear-btn');
    const ctx = canvas.getContext('2d');
    let isDrawing = false;
    let lastX = 0; let lastY = 0;
    ctx.strokeStyle = '#000'; ctx.lineWidth = 5; ctx.lineCap = 'round';
    function draw(e) {
        if (!isDrawing) return;
        ctx.beginPath();
        ctx.moveTo(lastX, lastY);
        ctx.lineTo(e.offsetX, e.offsetY);
        ctx.stroke();
        [lastX, lastY] = [e.offsetX, e.offsetY];
    }
    canvas.addEventListener('mousedown', (e) => {
        isDrawing = true;
        [lastX, lastY] = [e.offsetX, e.offsetY];
    });
    canvas.addEventListener('mousemove', draw);
    canvas.addEventListener('mouseup', () => isDrawing = false);
    canvas.addEventListener('mouseout', () => isDrawing = false);
    clearBtn.addEventListener('click', () => ctx.clearRect(0, 0, canvas.width, canvas.height));
</script></body></html>

8. Countdown Timer

A timer that counts down to a specific date.

  • Skills Practiced: JavaScript `Date` object, `setInterval()`, time calculations.

Core Logic:

  1. Set a target future date.
  2. Use `setInterval` to run a function every 1000 milliseconds (1 second).
  3. Inside the function, get the current time.
  4. Calculate the difference between the target time and current time in milliseconds.
  5. Convert the difference to days, hours, minutes, and seconds using math (`%` and `/`).
  6. Update the DOM with the new values.

Example Code:

<!DOCTYPE html>
<html><head><title>Countdown Timer</title>
<style>
    body { font-family: sans-serif; background: #111; color: white; display: grid; place-items: center; height: 100vh; margin: 0;}
    .timer { text-align: center; }
    .time-block { display: inline-block; margin: 0 15px; }
    .time-block span { display: block; font-size: 3rem; }
</style></head><body>
<div class="timer">
    <h1>New Year's Eve Countdown</h1>
    <div>
        <div class="time-block"><span id="days">0</span>Days</div>
        <div class="time-block"><span id="hours">0</span>Hours</div>
        <div class="time-block"><span id="minutes">0</span>Minutes</div>
        <div class="time-block"><span id="seconds">0</span>Seconds</div>
    </div>
</div>
<script>
    const daysEl = document.getElementById('days');
    const hoursEl = document.getElementById('hours');
    const minutesEl = document.getElementById('minutes');
    const secondsEl = document.getElementById('seconds');
    const targetDate = new Date('Jan 1, 2026 00:00:00').getTime();
    function countdown() {
        const now = new Date().getTime();
        const distance = targetDate - now;
        if (distance < 0) {
            clearInterval(timerInterval);
            document.querySelector('.timer h1').textContent = "Happy New Year!";
            return;
        }
        const days = Math.floor(distance / (1000 * 60 * 60 * 24));
        const hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
        const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
        const seconds = Math.floor((distance % (1000 * 60)) / 1000);
        daysEl.textContent = days;
        hoursEl.textContent = hours.toString().padStart(2, '0');
        minutesEl.textContent = minutes.toString().padStart(2, '0');
        secondsEl.textContent = seconds.toString().padStart(2, '0');
    }
    const timerInterval = setInterval(countdown, 1000);
    countdown(); // Initial call
</script></body></html>

9. Rock, Paper, Scissors Game

The classic game against the computer.

  • Skills Practiced: Game logic, random number generation, comparing choices, updating scores.

Core Logic:

  1. Add event listeners to the "Rock", "Paper", and "Scissors" buttons.
  2. When a user clicks, get their choice.
  3. Generate a random choice for the computer.
  4. Compare the two choices to determine a winner, loser, or a tie.
  5. Update the DOM to show the choices, the result, and update the scores.

Example Code:

<!DOCTYPE html>
<html><head><title>Rock Paper Scissors</title>
<style>
    body { font-family: sans-serif; text-align: center; background: #333; color: white; }
    .choices button { font-size: 2rem; margin: 10px; padding: 10px; cursor: pointer; }
    .result { margin-top: 20px; }
    .scores { margin-top: 20px; }
</style></head><body>
<h1>Rock, Paper, Scissors</h1>
<div class="choices">
    <button id="rock">✊</button>
    <button id="paper">✋</button>
    <button id="scissors">✌️</button>
</div>
<div class="result">
    <h2 id="result-text">Choose your move!</h2>
    <p>You chose: <span id="user-choice"></span></p>
    <p>Computer chose: <span id="computer-choice"></span></p>
</div>
<div class="scores">
    <h3>Score: You <span id="user-score">0</span> - <span id="computer-score">0</span> Computer</h3>
</div>
<script>
    const choices = ['rock', 'paper', 'scissors'];
    const buttons = document.querySelectorAll('.choices button');
    const resultText = document.getElementById('result-text');
    const userChoiceEl = document.getElementById('user-choice');
    const computerChoiceEl = document.getElementById('computer-choice');
    const userScoreEl = document.getElementById('user-score');
    const computerScoreEl = document.getElementById('computer-score');
    let userScore = 0; let computerScore = 0;
    
    buttons.forEach(button => button.addEventListener('click', () => {
        const userChoice = button.id;
        const computerChoice = choices[Math.floor(Math.random() * choices.length)];
        playRound(userChoice, computerChoice);
    }));

    function playRound(user, computer) {
        userChoiceEl.textContent = user;
        computerChoiceEl.textContent = computer;
        if (user === computer) {
            resultText.textContent = "It's a tie!";
        } else if (
            (user === 'rock' && computer === 'scissors') ||
            (user === 'paper' && computer === 'rock') ||
            (user === 'scissors' && computer === 'paper')
        ) {
            resultText.textContent = "You win!";
            userScore++;
        } else {
            resultText.textContent = "You lose!";
            computerScore++;
        }
        userScoreEl.textContent = userScore;
        computerScoreEl.textContent = computerScore;
    }
</script></body></html>

10. Recipe Finder App

An app where users can search for recipes based on an ingredient.

  • Skills Practiced: Fetching data from a public API, displaying results in a grid/card layout.

Core Logic:

  1. Find a free recipe API (e.g., TheMealDB, which doesn't require a key).
  2. Get the search term (ingredient) from the user.
  3. Use `fetch()` to call the API.
  4. Parse the JSON response and loop through the array of meals.
  5. For each meal, dynamically create an HTML card with its image and title, and append it to the results container.

Example Code:

<!DOCTYPE html>
<html><head><title>Recipe Finder</title>
<style>
    body { font-family: sans-serif; background: #222; color: #eee; }
    .container { max-width: 900px; margin: auto; text-align: center; }
    #recipe-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin-top: 20px; }
    .recipe-card { background: #333; border-radius: 8px; overflow: hidden; }
    .recipe-card img { width: 100%; }
    .recipe-card h3 { padding: 10px; margin: 0; }
</style></head><body>
<div class="container">
    <h1>Recipe Finder</h1>
    <input type="text" id="search-input" placeholder="Enter an ingredient (e.g., chicken)">
    <button id="search-btn">Search</button>
    <div id="recipe-grid"></div>
</div>
<script>
    const searchInput = document.getElementById('search-input');
    const searchBtn = document.getElementById('search-btn');
    const recipeGrid = document.getElementById('recipe-grid');
    
    async function getRecipes() {
        const ingredient = searchInput.value.trim();
        if (!ingredient) { alert('Please enter an ingredient'); return; }
        const apiUrl = `https://www.themealdb.com/api/json/v1/1/filter.php?i=${ingredient}`;
        try {
            recipeGrid.innerHTML = '<p>Loading...</p>';
            const response = await fetch(apiUrl);
            const data = await response.json();
            recipeGrid.innerHTML = '';
            if (data.meals) {
                data.meals.forEach(meal => {
                    const recipeCard = document.createElement('div');
                    recipeCard.classList.add('recipe-card');
                    recipeCard.innerHTML = `
                        <img src="${meal.strMealThumb}" alt="${meal.strMeal}">
                        <h3>${meal.strMeal}</h3>
                    `;
                    recipeGrid.appendChild(recipeCard);
                });
            } else {
                recipeGrid.innerHTML = '<p>No recipes found. Try another ingredient.</p>';
            }
        } catch (error) {
            recipeGrid.innerHTML = '<p>Something went wrong. Please try again later.</p>';
        }
    }
    searchBtn.addEventListener('click', getRecipes);
</script></body></html>

Conclusion: Keep Building! 🚀

Completing these projects will give you a strong, practical foundation in frontend development. The key is not just to build them, but to **customize and expand** them. Add new features, improve the styling, and make them your own. This is the best way to learn and build a portfolio to showcase your skills.