Build an Interactive Quiz App

Build a browser-based quiz app with JavaScript DOM manipulation.

Introduction

30 Second Summary

Quizzes are everywhere. From learning platforms to social media, they turn passive reading into active recall. Building one from scratch teaches you exactly how interactive web apps work under the hood.

In this project, you will build a browser-based quiz app using HTML, CSS, and JavaScript. Your app will present multiple-choice questions, give instant visual feedback on each answer, and display a final score.

What You'll Build

A multiple-choice quiz app that renders questions dynamically, responds to clicks with color-coded feedback, and tracks your score across a full round of questions.

By the end of this project, you'll have:

  • A clean HTML page with a card-style quiz layout, answer buttons, and a results screen.
  • JavaScript logic that dynamically creates elements, handles user clicks, and manages quiz state across multiple screens.
  • CSS styling with color-coded correct and wrong feedback states, hover effects, and smooth transitions.
  • Secret Mission: Add a countdown timer to each question that auto-advances when time runs out.

Are there any prerequisites?

None. This project includes full setup instructions for every tool you need. You do not need any prior coding experience to follow along.

Set Up Your Development Environment

Before you can build the quiz app, you need two things: a code editor to write your HTML, CSS, and JavaScript, and a way to preview your work in the browser instantly.

In this step, you'll install Visual Studio Code as your editor and add the Live Server extension for automatic browser refresh. Then you'll create the project folder with three starter files.

In this step, get ready to:
  • Install Visual Studio Code.
  • Install the Live Server extension.
  • Create the project folder and starter files.
Install Visual Studio Code

Visual Studio Code (VS Code) is a free code editor with built-in JavaScript support. It gives you syntax highlighting, auto-complete, and an integrated terminal all in one window.

  • Open your browser and go to code.visualstudio.com/download.
  • Download the installer for your operating system (the site auto-detects it for you).
  • Run the installer and follow the prompts to complete the installation.

Already have VS Code?

If VS Code is already installed on your machine, skip ahead to the next substep.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Visual Studio Code and press Enter to open it.
Install the Live Server extension

Live Server is a VS Code extension that runs a local development server. Every time you save a file, it automatically refreshes your browser so you can see changes instantly.

  • In VS Code, press Cmd+P (macOS) or Ctrl+P (Windows) to open Quick Open.
  • Type the following command and press Enter:
ext install ritwickdey.liveserver
  • Wait a few seconds for the extension to install. You should see a notification confirming the installation in the bottom-right corner of VS Code.

What is Live Server?

Live Server creates a mini web server on your computer at port 5500. When you save a file, it sends a signal to your browser to reload the page automatically. This means you never have to manually refresh.

Extension not installing?

Make sure you typed the command into the Quick Open bar (the small input at the top of VS Code), not the integrated terminal at the bottom. Quick Open opens with Cmd+P or Ctrl+P.

help me install the Live Server extension in VS Code

Create your project folder and files

Every web project needs a home folder. You'll create a folder called quiz-app on your Desktop and add three empty files inside it: one for HTML structure, one for CSS styling, and one for JavaScript logic.

  • Open VS Code's integrated terminal by clicking Terminal in the top menu bar, then selecting New Terminal.
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir quiz-app
cd quiz-app
  • Now open this folder as your VS Code workspace. Click File in the top menu bar.
  • Click Open Folder.
  • Navigate to your Desktop, select the quiz-app folder, and click Open (macOS) or Select Folder (Windows).

Why open the folder as a workspace?

Opening a folder in VS Code makes it your workspace. The Explorer sidebar shows all files inside it, and the integrated terminal starts in that folder automatically. This keeps everything organized in one place.

VS Code may reload when you open the folder. Once it finishes, you'll see quiz-app in the Explorer sidebar on the left.

  • Open a new terminal by clicking Terminal in the top menu bar, then selecting New Terminal. The terminal now starts inside your quiz-app folder.
  • Create the three project files by running this command:

macOS

touch index.html style.css script.js

Windows

New-Item index.html -ItemType File
New-Item style.css -ItemType File
New-Item script.js -ItemType File
  • Confirm the files exist by running:
ls

You should see index.html, style.css, and script.js in the output. You'll also see them appear in the Explorer sidebar on the left.

Don't see your files?

Make sure your terminal prompt shows you're inside the quiz-app folder. If you're in a different location, run cd ~/Desktop/quiz-app and try the file creation command again.

On Windows, if New-Item is not recognized, make sure your terminal is using PowerShell (not Command Prompt). Check the dropdown in the top-right corner of the terminal panel.

help me create files in my VS Code terminal

Your workspace is ready. Next up, you'll build the HTML structure that gives the quiz app its skeleton.

Build the HTML Structure

Your development environment is ready. Now it's time to build the foundation your quiz app will run on.

Every interactive web page starts with HTML that defines the structure of what users see. For the quiz app, JavaScript needs specific elements to grab onto. By giving each section a unique ID, your code can find and update the right parts of the page at the right time.

In this step, get ready to:
  • Create an HTML document with a quiz container and a results screen.
  • Link your CSS and JavaScript files to the page.
  • Launch Live Server and confirm the page loads in your browser.
Set up the document head and quiz container

The HTML file has two main visual sections. The first is the quiz container where questions and answer buttons appear. The second is a results screen that stays hidden until the quiz is over.

  • Open index.html in VS Code and paste the following code:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Quiz App</title>
    <!-- Connect our stylesheet for visual styling -->
    <link rel="stylesheet" href="style.css">
    <!-- Load our script after the HTML finishes parsing -->
    <script src="script.js" defer></script>
</head>
<body>
    <div class="quiz-container" id="quiz-container">
        <div class="quiz-header">
            <span id="question-counter">Question 1 of 5</span>
            <span id="score-display">Score: 0</span>
        </div>
        <h2 id="question-text">Question goes here</h2>
        <!-- JavaScript will create answer buttons inside here -->
        <div id="options-container"></div>
        <button id="next-btn" class="next-btn" style="display: none;">Next</button>
    </div>

What does this code do?

The <head> section connects your two other files. The <link> tag pulls in style.css for visual styling. The <script> tag with the defer attribute loads script.js only after the browser finishes reading all the HTML.

Without defer, JavaScript might try to find elements that haven't loaded yet and crash with a null reference error.

The quiz container holds everything the player sees during gameplay. options-container starts empty because JavaScript will create answer buttons dynamically for each question. The Next button is hidden (display: none) until the player picks an answer.

  • Don't save yet. The next chunk completes the HTML structure.
Add the results screen

When the player finishes all questions, the quiz container disappears and a results screen takes its place. This section stays hidden until JavaScript reveals it.

  • In index.html, add this code directly below the closing </div> of the quiz-container (right after the last line you pasted):
    <!-- Results screen, hidden until the quiz ends -->
    <div class="results-container" id="results-container" style="display: none;">
        <h2>Quiz Complete!</h2>
        <p id="results-text"></p>
        <button id="restart-btn" class="restart-btn">Play Again</button>
    </div>
</body>
</html>

What does this code do?

The results container shows the player's final score once all questions are answered. Like the Next button, it starts with style="display: none;" so it's invisible on page load.

The results-text paragraph is empty because JavaScript will fill it with the score and percentage at the end. The Play Again button will reset everything back to question one.

  • Save index.html.
Launch Live Server to verify

Live Server runs a local web server that automatically refreshes your browser whenever you save a file. This makes development much faster.

  • In VS Code, make sure index.html is the active file (its tab is selected).
  • Click Go Live in the bottom-right corner of the VS Code status bar.
  • Your browser opens automatically. Confirm the page loads at http://127.0.0.1:5500.

You should see a mostly blank page with the text "Question 1 of 5", "Score: 0", "Question goes here", and "Play Again" visible. The page has no styling yet, so the text appears plain.

Don't see Go Live in the status bar?

The Go Live button only appears when you have an HTML file open as the active tab. Click the index.html tab in VS Code, then check the status bar again.

If you still don't see it, the Live Server extension may not have installed correctly. Press Ctrl+P, type ext install ritwickdey.liveserver, and press Enter to reinstall it.

help me troubleshoot Live Server not appearing in VS Code

✔️ Awesome, I've got everything!

Great. Double check you've saved index.html and your browser is showing text content from the page.

ⓧ I'd like to double check the full code

Here is the complete index.html file. Make sure yours matches exactly:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Quiz App</title>
    <link rel="stylesheet" href="style.css">
    <script src="script.js" defer></script>
</head>
<body>
    <div class="quiz-container" id="quiz-container">
        <div class="quiz-header">
            <span id="question-counter">Question 1 of 5</span>
            <span id="score-display">Score: 0</span>
        </div>
        <h2 id="question-text">Question goes here</h2>
        <div id="options-container"></div>
        <button id="next-btn" class="next-btn" style="display: none;">Next</button>
    </div>
    <div class="results-container" id="results-container" style="display: none;">
        <h2>Quiz Complete!</h2>
        <p id="results-text"></p>
        <button id="restart-btn" class="restart-btn">Play Again</button>
    </div>
</body>
</html>

Your HTML skeleton is in place. Next up, you'll add CSS styling to turn this plain text into a polished quiz card with color-coded feedback states.

Style the Quiz Card

Your HTML skeleton is complete, but right now the page shows plain unstyled text on a white background. Without CSS, the browser has no instructions for how to arrange or color your elements.

In this step, you'll transform the raw HTML into a polished card interface with a centered layout, interactive buttons, and color-coded feedback classes that will show right and wrong answers.

In this step, get ready to:
  • Style the page background and center a card layout using flexbox.
  • Add interactive button styles with hover effects and correct/wrong feedback classes.
  • Style the navigation buttons and results screen.
Style the page background and card container

Every polished web app starts with a reset that removes default browser spacing, and a page-level layout that centers the main content.

  • Open style.css in VS Code and add the page foundation styles by pasting this code:
/* Reset default browser spacing */
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

/* Center the quiz card on the page */
body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    background-color: #f0f2f5;
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 100vh;
}

What does this code do?

The * selector targets every element on the page and removes default margins and padding that browsers add. box-sizing: border-box makes width calculations include padding and borders, which prevents layout surprises.

The body uses display: flex with justify-content: center and align-items: center to center everything both horizontally and vertically. min-height: 100vh ensures the body fills the full browser window height.

  • Save style.css and check your browser.
  • You should see the page background change to a light grey color.

The card itself still looks like plain text because you haven't styled the container yet. That comes next.

  • Add the card container and internal structure styles below your body rule by pasting this code:
/* Card container shared by quiz and results */
.quiz-container,
.results-container {
    background: #fff;
    border-radius: 12px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
    padding: 32px;
    width: 100%;
    max-width: 500px;
}

/* Header row with question counter and score */
.quiz-header {
    display: flex;
    justify-content: space-between;
    margin-bottom: 20px;
    font-size: 14px;
    color: #666;
}

/* Question text styling */
#question-text {
    font-size: 20px;
    margin-bottom: 24px;
    color: #333;
}

/* Container for answer buttons */
#options-container {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

What does this code do?

The .quiz-container and .results-container share the same card styles: a white background, rounded corners, a soft shadow, and a max width of 500px to keep the card readable.

The .quiz-header uses justify-content: space-between to push the question counter and score to opposite edges. The #options-container stacks answer buttons vertically with a 12px gap between them.

  • Save style.css and refresh your browser.
  • You should see a centered white card with rounded corners and a subtle shadow. Inside it, you'll see "Question 1 of 5" and "Score: 0" spaced apart at the top, and "Question goes here" in dark text below.

Don't see a white card?

  • Make sure your style.css file is saved. Unsaved changes won't appear in the browser.
  • Check that your index.html has <link rel="stylesheet" href="style.css"> in the <head>. Without this link, the browser can't find your styles.
  • Confirm the filename is exactly style.css with no typos or extra spaces.

Why isn't my CSS showing up in the browser?

Add option button styles with feedback states

The answer buttons need to feel interactive. They should change appearance when hovered, turn green for a correct answer, and turn red for a wrong one.

  • Add the option button base styles and hover effect below your #options-container rule:
/* Answer option buttons */
.option-btn {
    padding: 14px 20px;
    border: 2px solid #e0e0e0;
    border-radius: 8px;
    background: #fff;
    font-size: 16px;
    cursor: pointer;
    text-align: left;
    transition: background-color 0.3s, border-color 0.3s, color 0.3s;
}

/* Hover state for interactive feel */
.option-btn:hover {
    border-color: #4a90d9;
    background-color: #f7faff;
}

What does this code do?

The .option-btn class creates clean, rounded buttons with a light border. The transition property makes color changes animate over 0.3 seconds instead of snapping instantly. This creates smooth visual feedback when answers are revealed.

The :hover state adds a blue border and faint blue background when the user moves their cursor over a button.

  • Now add the feedback state classes below the hover rule. These classes will be applied by JavaScript when the user selects an answer:
/* Correct answer - green */
.option-btn.correct {
    background-color: #d4edda;
    border-color: #28a745;
    color: #155724;
}

/* Wrong answer - red */
.option-btn.wrong {
    background-color: #f8d7da;
    border-color: #dc3545;
    color: #721c24;
}

/* Disable buttons after answering */
.option-btn.disabled {
    pointer-events: none;
    opacity: 0.8;
}

What does this code do?

The .correct class turns a button green. The .wrong class turns it red. JavaScript will add these classes to the appropriate buttons after the user clicks an answer.

The .disabled class uses pointer-events: none to prevent any further clicks. This stops users from clicking multiple answers on the same question.

  • Save style.css.
  • You won't see option buttons in the browser yet. They will be created dynamically by JavaScript in the next step. The styles you've written will apply automatically once the buttons exist.
Style the navigation buttons and results section

The quiz needs a "Next" button to advance between questions and a "Play Again" button on the results screen. Both should be full-width and visually distinct from the answer options.

  • Add the navigation button styles below your .option-btn.disabled rule:
/* Next and Play Again buttons */
.next-btn,
.restart-btn {
    margin-top: 24px;
    padding: 12px 32px;
    background-color: #4a90d9;
    color: #fff;
    border: none;
    border-radius: 8px;
    font-size: 16px;
    cursor: pointer;
    display: block;
    width: 100%;
}

.next-btn:hover,
.restart-btn:hover {
    background-color: #357abd;
}

What does this code do?

Both .next-btn and .restart-btn share the same styling: a solid blue background, white text, full width, and rounded corners. The hover state darkens the blue slightly so the user knows the button is clickable.

  • Add the results section styles below the hover rule to finish your stylesheet:
/* Results screen layout */
.results-container {
    text-align: center;
}

.results-container h2 {
    font-size: 28px;
    color: #333;
    margin-bottom: 16px;
}

#results-text {
    font-size: 18px;
    color: #555;
    margin-bottom: 8px;
}

What does this code do?

The .results-container centers all text inside the results card. The heading gets a larger font size for visual impact, and the score text is styled in a muted grey so it doesn't compete with the heading.

  • Save style.css and refresh your browser.
  • The visible card still shows the question header and placeholder text from before. The results screen and navigation buttons are hidden until JavaScript reveals them.

Card looks different than expected?

  • Make sure you haven't accidentally placed a rule inside another rule's closing brace. Each selector block should be at the top level of the file.
  • Check for missing semicolons at the end of property lines. A missing semicolon can cause the browser to skip the following property.
  • Verify class names match exactly between your HTML and CSS. For example, .quiz-container in CSS must match class="quiz-container" in HTML.

Why does my quiz card look broken or unstyled?

✔️ Awesome, I've got everything!

Double check you've saved style.css and your browser shows the styled card.

ⓧ I'd like to double check the full code

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    background-color: #f0f2f5;
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 100vh;
}

.quiz-container,
.results-container {
    background: #fff;
    border-radius: 12px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
    padding: 32px;
    width: 100%;
    max-width: 500px;
}

.quiz-header {
    display: flex;
    justify-content: space-between;
    margin-bottom: 20px;
    font-size: 14px;
    color: #666;
}

#question-text {
    font-size: 20px;
    margin-bottom: 24px;
    color: #333;
}

#options-container {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.option-btn {
    padding: 14px 20px;
    border: 2px solid #e0e0e0;
    border-radius: 8px;
    background: #fff;
    font-size: 16px;
    cursor: pointer;
    text-align: left;
    transition: background-color 0.3s, border-color 0.3s, color 0.3s;
}

.option-btn:hover {
    border-color: #4a90d9;
    background-color: #f7faff;
}

.option-btn.correct {
    background-color: #d4edda;
    border-color: #28a745;
    color: #155724;
}

.option-btn.wrong {
    background-color: #f8d7da;
    border-color: #dc3545;
    color: #721c24;
}

.option-btn.disabled {
    pointer-events: none;
    opacity: 0.8;
}

.next-btn,
.restart-btn {
    margin-top: 24px;
    padding: 12px 32px;
    background-color: #4a90d9;
    color: #fff;
    border: none;
    border-radius: 8px;
    font-size: 16px;
    cursor: pointer;
    display: block;
    width: 100%;
}

.next-btn:hover,
.restart-btn:hover {
    background-color: #357abd;
}

.results-container {
    text-align: center;
}

.results-container h2 {
    font-size: 28px;
    color: #333;
    margin-bottom: 16px;
}

#results-text {
    font-size: 18px;
    color: #555;
    margin-bottom: 8px;
}

Your quiz card looks polished and ready for interaction. Next up, you'll write the JavaScript that fills this interface with real questions and makes the buttons respond to clicks.

Create the Quiz Data and Core Logic

Your quiz card looks great, but it's still static. The placeholder text just sits there no matter what you do.

In this step, you'll bring the quiz to life with JavaScript. You'll create a data structure that holds your questions, then write functions that dynamically render each question and respond to the user's clicks.

In this step, get ready to:
  • Define quiz questions as an array of objects.
  • Write a function that renders questions and answer buttons dynamically.
  • Write a function that checks the user's answer and gives visual feedback.
Define the quiz data as an array of objects

Every quiz needs questions stored somewhere. In JavaScript, you can use an array of objects to hold structured data like questions, answer choices, and correct answers.

  • Open script.js in VS Code and add the quiz data array by pasting this code:
// Quiz questions stored as an array of objects
const quizData = [
    {
        question: "What does HTML stand for?",
        options: [
            "Hyper Text Markup Language",
            "High Tech Modern Language",
            "Home Tool Markup Language",
            "Hyperlink and Text Markup Language"
        ],
        correctAnswer: 0
    },
    {
        question: "Which CSS property is used to change text color?",
        options: [
            "font-color",
            "text-color",
            "color",
            "foreground-color"
        ],
        correctAnswer: 2
    },
    {
        question: "What keyword declares a variable that cannot be reassigned?",
        options: [
            "var",
            "let",
            "const",
            "static"
        ],
        correctAnswer: 2
    },
    {
        question: "Which method selects an element by its ID?",
        options: [
            "document.querySelector()",
            "document.getElementById()",
            "document.getElement()",
            "document.findById()"
        ],
        correctAnswer: 1
    },
    {
        question: "What does CSS stand for?",
        options: [
            "Creative Style Sheets",
            "Computer Style Syntax",
            "Cascading Style Sheets",
            "Colorful Style Sheets"
        ],
        correctAnswer: 2
    }
];

What does this code do?

quizData is a const array where each item is an object representing one question.

  • question holds the question text as a string.
  • options is an array of 4 possible answers.
  • correctAnswer is the index (starting from 0) of the correct option in the array.
  • Save script.js.

You won't see a change in the browser yet. The data is stored but nothing is using it.

Seeing a syntax error in the console?

  • Check that every object in the array ends with a closing brace } followed by a comma (except the last one).
  • Make sure all strings use matching quote marks. Opening and closing quotes must be the same type.

Help me find the syntax error in my quizData array

Next, you need to set up the variables that track the quiz's state and connect your JavaScript to the HTML elements on the page.

  • Below the quizData array, add the state variables and DOM references by pasting this code:
// Track which question we're on and the player's score
let currentQuestion = 0;
let score = 0;

// Grab references to all the HTML elements we'll update
const quizContainer = document.getElementById("quiz-container");
const resultsContainer = document.getElementById("results-container");
const questionText = document.getElementById("question-text");
const optionsContainer = document.getElementById("options-container");
const questionCounter = document.getElementById("question-counter");
const scoreDisplay = document.getElementById("score-display");
const nextBtn = document.getElementById("next-btn");
const restartBtn = document.getElementById("restart-btn");
const resultsText = document.getElementById("results-text");

What does this code do?

  • currentQuestion tracks which question index the player is on (starts at 0).
  • score counts how many correct answers the player has given.
  • The document.getElementById() calls grab references to your HTML elements so JavaScript can read and update them later.
  • Save script.js.
  • Refresh your browser. You should still see the same quiz card as before. No errors in the console means your DOM references are connecting successfully.

Open the browser console

Press F12 or Ctrl+Shift+J (Windows) to open Chrome DevTools. The Console tab shows JavaScript errors. If it's empty, your code is running without issues.

Write the loadQuestion function

The quiz needs a function that pulls the current question from the array and builds the answer buttons on the fly. This is DOM manipulation in action. Instead of hardcoding buttons in HTML, JavaScript creates them dynamically each time a new question loads.

  • Below your DOM references in script.js, add the loadQuestion() function by pasting this code:
function loadQuestion() {
    // Get the current question object from the array
    const data = quizData[currentQuestion];
    questionText.textContent = data.question;
    questionCounter.textContent = "Question " + (currentQuestion + 1) + " of " + quizData.length;
    // Clear old buttons and hide the Next button
    optionsContainer.innerHTML = "";
    nextBtn.style.display = "none";

    // Create a button for each answer option
    data.options.forEach(function (option, index) {
        const button = document.createElement("button");
        button.textContent = option;
        button.classList.add("option-btn");
        // When clicked, check if this option is correct
        button.addEventListener("click", function () {
            selectAnswer(index, button);
        });
        optionsContainer.appendChild(button);
    });
}

What does this code do?

  • quizData[currentQuestion] pulls the current question object from the array using the index.
  • optionsContainer.innerHTML = "" clears any previously rendered buttons before creating new ones.
  • document.createElement("button") creates a brand new button element in memory. You then set its text, add a class, and attach a click listener before appending it to the page.
  • The forEach loop runs once for each option, creating 4 buttons total.

The function is defined but not called yet. You need to invoke it to see results.

  • At the very bottom of script.js (after the function definition), add this line to start the quiz:
loadQuestion();
  • Save script.js and refresh your browser.
  • You should see the first question ("What does HTML stand for?") displayed in the card with four clickable answer buttons below it.

Still seeing placeholder text?

  • Make sure the loadQuestion(); call is at the very bottom of the file, outside any function.
  • Check that the element IDs in your getElementById calls match the IDs in your HTML exactly (they are case-sensitive).
  • Open the browser console (F12) and look for red error messages.

Help me figure out why my quiz question isn't loading

Write the selectAnswer function

Clicking an answer button should trigger visual feedback. The correct answer turns green, a wrong answer turns red, and all buttons become disabled to prevent multiple clicks.

  • In script.js, find the closing brace of your loadQuestion() function. Paste the selectAnswer() function below it (but above the loadQuestion(); call at the bottom):
function selectAnswer(selectedIndex, selectedButton) {
    const data = quizData[currentQuestion];
    const allButtons = optionsContainer.querySelectorAll(".option-btn");

    // Disable all buttons so the user can't click again
    allButtons.forEach(function (btn) {
        btn.classList.add("disabled");
    });

    // Check if the selected answer is correct
    if (selectedIndex === data.correctAnswer) {
        selectedButton.classList.add("correct");
        score++;
        scoreDisplay.textContent = "Score: " + score;
    } else {
        selectedButton.classList.add("wrong");
        // Highlight the correct answer so the user can learn
        allButtons[data.correctAnswer].classList.add("correct");
    }

    // Reveal the Next button to advance
    nextBtn.style.display = "block";
}

What does this code do?

  • querySelectorAll(".option-btn") grabs all four buttons so you can disable them all at once.
  • Adding the disabled class applies the CSS rule pointer-events: none which prevents further clicks.
  • The if/else compares the clicked button's index against correctAnswer. A match adds the green correct class. A mismatch adds the red wrong class and also reveals the correct answer in green.
  • Showing the nextBtn lets the user advance to the next question after seeing the feedback.
  • Save script.js and refresh your browser.
  • Click any answer button. If you click the correct answer, it turns green. If you click a wrong answer, it turns red and the correct one is highlighted green.
  • After clicking, all buttons should become unclickable and the Next button should appear below them.

Buttons not changing color?

  • Make sure your style.css has the .option-btn.correct and .option-btn.wrong classes defined from the previous step.
  • Check that the selectAnswer function is placed between loadQuestion() (the function) and loadQuestion(); (the call at the bottom).
  • Verify there are no red errors in the browser console (F12).

Help me debug why clicking an answer doesn't change the button color

✔️ Awesome, I've got everything!

Great. Double check you've saved your file before moving on.

ⓧ I'd like to double check the full code

Here's the complete script.js file at this point:

const quizData = [
    {
        question: "What does HTML stand for?",
        options: [
            "Hyper Text Markup Language",
            "High Tech Modern Language",
            "Home Tool Markup Language",
            "Hyperlink and Text Markup Language"
        ],
        correctAnswer: 0
    },
    {
        question: "Which CSS property is used to change text color?",
        options: [
            "font-color",
            "text-color",
            "color",
            "foreground-color"
        ],
        correctAnswer: 2
    },
    {
        question: "What keyword declares a variable that cannot be reassigned?",
        options: [
            "var",
            "let",
            "const",
            "static"
        ],
        correctAnswer: 2
    },
    {
        question: "Which method selects an element by its ID?",
        options: [
            "document.querySelector()",
            "document.getElementById()",
            "document.getElement()",
            "document.findById()"
        ],
        correctAnswer: 1
    },
    {
        question: "What does CSS stand for?",
        options: [
            "Creative Style Sheets",
            "Computer Style Syntax",
            "Cascading Style Sheets",
            "Colorful Style Sheets"
        ],
        correctAnswer: 2
    }
];

let currentQuestion = 0;
let score = 0;

const quizContainer = document.getElementById("quiz-container");
const resultsContainer = document.getElementById("results-container");
const questionText = document.getElementById("question-text");
const optionsContainer = document.getElementById("options-container");
const questionCounter = document.getElementById("question-counter");
const scoreDisplay = document.getElementById("score-display");
const nextBtn = document.getElementById("next-btn");
const restartBtn = document.getElementById("restart-btn");
const resultsText = document.getElementById("results-text");

function loadQuestion() {
    const data = quizData[currentQuestion];
    questionText.textContent = data.question;
    questionCounter.textContent = "Question " + (currentQuestion + 1) + " of " + quizData.length;
    optionsContainer.innerHTML = "";
    nextBtn.style.display = "none";

    data.options.forEach(function (option, index) {
        const button = document.createElement("button");
        button.textContent = option;
        button.classList.add("option-btn");
        button.addEventListener("click", function () {
            selectAnswer(index, button);
        });
        optionsContainer.appendChild(button);
    });
}

function selectAnswer(selectedIndex, selectedButton) {
    const data = quizData[currentQuestion];
    const allButtons = optionsContainer.querySelectorAll(".option-btn");

    allButtons.forEach(function (btn) {
        btn.classList.add("disabled");
    });

    if (selectedIndex === data.correctAnswer) {
        selectedButton.classList.add("correct");
        score++;
        scoreDisplay.textContent = "Score: " + score;
    } else {
        selectedButton.classList.add("wrong");
        allButtons[data.correctAnswer].classList.add("correct");
    }

    nextBtn.style.display = "block";
}

loadQuestion();

Your quiz now loads questions and gives instant feedback on answers. Next up, you'll wire up the Next button to advance through all 5 questions and show a final results screen.

Handle Navigation and Results

Your quiz can now display questions and give instant feedback when you click an answer. The missing piece is flow control.

Right now, clicking the Next button does nothing. The quiz has no way to advance to the next question, show final results, or let the user play again. In this step, you'll wire up the navigation logic that ties the entire app together.

In this step, get ready to:
  • Write a function that advances to the next question or ends the quiz.
  • Display a results screen with the final score and percentage.
  • Add a restart function and connect all buttons with event listeners.
Write the nextQuestion() function

When a user clicks Next, the app needs to decide: is there another question in the array, or has the quiz ended? This decision is what nextQuestion() handles.

  • In script.js, find the closing } of the selectAnswer() function.
  • Add the following function below it, above the existing loadQuestion(); call at the bottom of the file:
function nextQuestion() {
    // Move to the next question in the array
    currentQuestion++;
    if (currentQuestion < quizData.length) {
        // More questions remain, load the next one
        loadQuestion();
    } else {
        // No more questions, show the results screen
        showResults();
    }
}

What does this code do?

currentQuestion++ increases the question index by 1, moving to the next position in the quizData array.

The if statement checks whether the new index is still within bounds. If yes, it loads the next question. If the index has gone past the last item, the quiz is over and it calls showResults() instead.

  • Save script.js.

You won't see a change yet. The Next button isn't connected to this function yet, and showResults() doesn't exist. The next two chunks will fix both of those.

Write the showResults() function

When the quiz ends, the app needs to swap out the question card for a results screen. This function hides the quiz container, reveals the results container, and calculates a final percentage score.

  • Add this function directly below nextQuestion():
function showResults() {
    // Hide the quiz card
    quizContainer.style.display = "none";
    // Reveal the results section
    resultsContainer.style.display = "block";
    // Calculate the percentage score
    const percentage = Math.round((score / quizData.length) * 100);
    // Display the score as a fraction and percentage
    resultsText.textContent = "You scored " + score + " out of " + quizData.length + " (" + percentage + "%)";
}

What does this code do?

The two style.display lines toggle visibility. Setting display to "none" removes the element from view. Setting it to "block" makes it visible.

Math.round() rounds the percentage to a whole number so the learner sees something like "80%" instead of "80.00000%".

  • Save script.js.

You still won't see a visible change. The next chunk connects the buttons and makes everything work together.

Write the restartQuiz() function and wire up event listeners

The last piece is a reset function that brings everything back to the starting state, plus the event listeners that connect your buttons to the functions you just wrote.

  • Add this code below showResults(), still above the final loadQuestion(); call at the bottom of the file:
function restartQuiz() {
    // Reset tracking variables to their starting values
    currentQuestion = 0;
    score = 0;
    scoreDisplay.textContent = "Score: 0";
    // Swap visibility back to the quiz card
    resultsContainer.style.display = "none";
    quizContainer.style.display = "block";
    // Load the first question again
    loadQuestion();
}

// Connect the Next button to the navigation function
nextBtn.addEventListener("click", nextQuestion);
// Connect the Play Again button to the restart function
restartBtn.addEventListener("click", restartQuiz);

What does this code do?

restartQuiz() resets both currentQuestion and score to 0, updates the score display on screen, swaps visibility back to the quiz card, and reloads the first question. It returns the app to its exact starting state.

The two addEventListener() calls connect the HTML buttons to your functions. When the user clicks Next, the browser calls nextQuestion(). When they click Play Again, it calls restartQuiz().

  • Save script.js and refresh your browser.
  • Answer all 5 questions by clicking an option, then clicking Next for each one. After the fifth question, you should see the results screen showing "Quiz Complete!" with your score as a fraction and percentage.
  • Click Play Again. The quiz should restart from Question 1 with the score reset to 0.

Next button still doesn't advance?

  • Make sure the two addEventListener lines are placed OUTSIDE any function, at the top level of the file (below restartQuiz() but above the final loadQuestion(); call).
  • Check that you spelled nextQuestion and restartQuiz exactly (no parentheses after the function name inside addEventListener).
  • Open the browser console (press F12 then click the Console tab) to check for red error messages.

My quiz buttons aren't advancing or restarting. Help me debug.

✔️ Awesome, I've got everything!

Great. Double check you've saved script.js and confirmed the full quiz flow works end to end.

ⓧ I'd like to double check the full code

Here is the complete script.js file. Compare yours against it line by line.

const quizData = [
    {
        question: "What does HTML stand for?",
        options: [
            "Hyper Text Markup Language",
            "High Tech Modern Language",
            "Home Tool Markup Language",
            "Hyperlink and Text Markup Language"
        ],
        correctAnswer: 0
    },
    {
        question: "Which CSS property is used to change text color?",
        options: [
            "font-color",
            "text-color",
            "color",
            "foreground-color"
        ],
        correctAnswer: 2
    },
    {
        question: "What keyword declares a variable that cannot be reassigned?",
        options: [
            "var",
            "let",
            "const",
            "static"
        ],
        correctAnswer: 2
    },
    {
        question: "Which method selects an element by its ID?",
        options: [
            "document.querySelector()",
            "document.getElementById()",
            "document.getElement()",
            "document.findById()"
        ],
        correctAnswer: 1
    },
    {
        question: "What does CSS stand for?",
        options: [
            "Creative Style Sheets",
            "Computer Style Syntax",
            "Cascading Style Sheets",
            "Colorful Style Sheets"
        ],
        correctAnswer: 2
    }
];

let currentQuestion = 0;
let score = 0;

const quizContainer = document.getElementById("quiz-container");
const resultsContainer = document.getElementById("results-container");
const questionText = document.getElementById("question-text");
const optionsContainer = document.getElementById("options-container");
const questionCounter = document.getElementById("question-counter");
const scoreDisplay = document.getElementById("score-display");
const nextBtn = document.getElementById("next-btn");
const restartBtn = document.getElementById("restart-btn");
const resultsText = document.getElementById("results-text");

function loadQuestion() {
    const data = quizData[currentQuestion];
    questionText.textContent = data.question;
    questionCounter.textContent = "Question " + (currentQuestion + 1) + " of " + quizData.length;
    optionsContainer.innerHTML = "";
    nextBtn.style.display = "none";

    data.options.forEach(function (option, index) {
        const button = document.createElement("button");
        button.textContent = option;
        button.classList.add("option-btn");
        button.addEventListener("click", function () {
            selectAnswer(index, button);
        });
        optionsContainer.appendChild(button);
    });
}

function selectAnswer(selectedIndex, selectedButton) {
    const data = quizData[currentQuestion];
    const allButtons = optionsContainer.querySelectorAll(".option-btn");

    allButtons.forEach(function (btn) {
        btn.classList.add("disabled");
    });

    if (selectedIndex === data.correctAnswer) {
        selectedButton.classList.add("correct");
        score++;
        scoreDisplay.textContent = "Score: " + score;
    } else {
        selectedButton.classList.add("wrong");
        allButtons[data.correctAnswer].classList.add("correct");
    }

    nextBtn.style.display = "block";
}

function nextQuestion() {
    currentQuestion++;
    if (currentQuestion < quizData.length) {
        loadQuestion();
    } else {
        showResults();
    }
}

function showResults() {
    quizContainer.style.display = "none";
    resultsContainer.style.display = "block";
    const percentage = Math.round((score / quizData.length) * 100);
    resultsText.textContent = "You scored " + score + " out of " + quizData.length + " (" + percentage + "%)";
}

function restartQuiz() {
    currentQuestion = 0;
    score = 0;
    scoreDisplay.textContent = "Score: 0";
    resultsContainer.style.display = "none";
    quizContainer.style.display = "block";
    loadQuestion();
}

nextBtn.addEventListener("click", nextQuestion);
restartBtn.addEventListener("click", restartQuiz);

loadQuestion();

Your quiz app is fully functional. Questions advance, scores are tracked, results display cleanly, and the whole thing resets at the press of a button. Next up, a secret mission to add time pressure with a countdown timer.

Secret mission

Add a Countdown Timer

Your quiz works great, but there's no pressure. Real quizzes have time limits that keep players on their toes. In this secret mission, add a 15-second countdown timer to each question that auto-advances when time runs out.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely on your computer with no cloud services or paid APIs. There are no ongoing costs.

Decide whether to keep your project files and tools installed, pause by closing everything, or delete them entirely.

Resources you used:

  • quiz-app project folder on your Desktop (contains index.html, style.css, script.js).
  • Live Server VS Code extension.
  • Visual Studio Code application.

Keep everything running

No action needed. Choose this if you plan to keep building on this project or want to reference the code later.

  • Your quiz-app folder stays on your Desktop.
  • Live Server remains available in VS Code for future projects.
  • VS Code stays installed and ready to use.

Pause - I'll come back to this later

Close everything to free up memory. Your files and tools stay installed for when you return.

  • Close the browser tab running Live Server.
  • In VS Code, click the Port: 5500 indicator in the status bar to stop Live Server.
  • Close VS Code.

Delete - I don't want to use this again

Remove all project files, the Live Server extension, and optionally VS Code itself.

  • Delete the project folder. On macOS/Linux, open your terminal and run the command below. On Windows, open PowerShell and use the Windows command below.

macOS

rm -rf ~/Desktop/quiz-app

Windows

Remove-Item -Recurse -Force "$env:USERPROFILE\Desktop\quiz-app"
  • Uninstall the Live Server extension by running this in your terminal:
code --uninstall-extension ritwickdey.liveserver
  • Uninstall VS Code (optional). On macOS, drag Visual Studio Code from Applications to the Trash. On Windows, open Settings, select Apps, find Microsoft Visual Studio Code, and click Uninstall.

That's a Wrap!

That's a Wrap!

Nice work! You just built a fully interactive quiz app from scratch using nothing but HTML, CSS, and vanilla JavaScript.

You've learned how to:

  • Dynamically created DOM elements with document.createElement() and attached event listeners to handle user clicks.
  • Managed application state (current question, score) across multiple screens using JavaScript variables and conditional logic.
  • Applied conditional CSS styling for real-time visual feedback, giving users instant green or red responses.
  • Added a countdown timer with setInterval() and clearInterval() to auto-advance questions when time runs out.

Ready to quiz yourself?