Create Multi-level Quiz App using HTML CSS JS

Looking for a fun way to learn DOM manipulation and event‑driven programming? This tutorial shows you how to craft a JavaScript multi-level quiz app that features instructions, timed questions, live scoring, multiple levels and a detailed results table — all inside a sleek, single‑page interface powered by HTML, CSS and vanilla JavaScript.

Project Overview

  • Timed questions: Each question has a 10‑second progress bar.
  • Dynamic scoring: Correct, wrong and unanswered tallies update in real time.
  • Multi‑level play: Finish one level to unlock the next.
  • Responsive UI: Flexbox layouts and CSS transitions give the quiz a polished look.
  • No frameworks: Pure HTML, CSS and JS keep the bundle lightweight and beginner‑friendly.

Tip: Because everything runs on the front end, this quiz is perfect for coding challenges, learning exercises or portfolio demos. For production, you’d pair it with a database or API.

HTML Structure (Quiz Layout)

The HTML acts as the foundation of our multi-level quiz app using HTML, CSS and JavaScript. Here’s what the structure includes:

<div id="main-container" class="centered-flex">
    <button class="play-btn">Play Quiz</button>
    <div class="quiz-container centered-flex hidden">
        <div class="instructions hidden">
            <section class="top">Instructions</section>
            <ul>
                <li>You have 10 seconds time to answer each question.</li>
                <li>If the timer runs out before you submit an answer, the question will be marked as unanswered.</li>
                <li>Once an option is selected, it cannot be changed.</li>
                <li>You will earn points for each correct answer.</li>
                <li>There is no penalty for incorrect answers, so feel free to guess if you're unsure.</li>
                <li>You can also see your total score at the top.</li>
                <li>After finishing the quiz, you'll see your final score and a summary of your answers.</li>
                <li>Click the "Play Quiz" button to begin the quiz.</li>
            </ul>
            <button class="start-quiz">Start Quiz</button>
        </div>
        <div class="game centered-flex hidden">
            <section class="top">
                <div class="level">Level:<span>1</span></div>
                <div class="score">Score:<span>0</span></div>
                <div class="timer"></div>
            </section>
            <section class="body">
                <div class="ques-container centered-flex">
                    <div class="ques-number"></div>
                    <div class="question centered-flex"></div>
                </div>
                <ol class="options centered-flex">
                    <li></li>
                    <li></li>
                    <li></li>
                    <li></li>
                </ol>
            </section>
            <section class="bottom centered-flex">
                <div class="question-count"><span>1</span> Questions</div>
                <button class="next-btn"></button>
            </section>
        </div>
        <div class="result hidden centered-flex">
            <div class="text">Congrats! you have Completed this Level</div>
            <div class="details">
                <table>
                    <thead>
                        <tr>
                            <th>Total</th>
                            <th>Answered</th>
                            <th>Unanswered</th>
                            <th>Right</th>
                            <th>Wrong</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr class="scorecard">
                            <td></td>
                            <td></td>
                            <td></td>
                            <td></td>
                            <td></td>
                        </tr>
                    </tbody>
                </table>
            </div>
            <div class="final-statement">
                You have got <span></span> out of <span></span>
            </div>
            <div class="action">
                <button class="exit">Exit Quiz</button>
                <button class="next-level">Next Level</button>
            </div>
        </div>
    </div>
</div>
  • A Play Quiz button to start the app.
  • An instruction screen explaining the rules.
  • The multi-level quiz app game container that dynamically loads questions.
  • A result section showing the summary and final score.

Each question includes:

  • Question text.
  • 4 options in an ordered list (<ol>).
  • A top bar showing score, level and timer.
  • A “Next” button to load the next question.

Why this structure works: It separates each stage (instructions, game, result) into its own div, which makes toggling visibility and managing logic easier with JavaScript.

CSS Styling (Design & Responsiveness)

The CSS gives our multi-level quiz app a modern, interactive feel. Here are some important highlights:

@import url('https://fonts.googleapis.com/css2?family=Fredoka:wght@300;400;500&display=swap');

html {
    font-size: 62.5%;
}

.centered-flex {
    display: flex;
    justify-content: center;
    align-items: center;
}

.hidden {
    visibility: hidden;
    opacity: 0;
}

.correctAnswer,
.incrorrectAnswer {
    transition: .3s;
    color: #e2e2e2 !important;
    border: 1px solid #bcbcbc !important;
}

.correctAnswer {
    background: #13a56a7a !important;
}

.incrorrectAnswer {
    background: #e94c4b4d !important;
}

#main-container {
    width: 100%;
    position: absolute;
    height: 100%;
    font-family: 'calibri';
}

button {
    padding: 5px 18px;
    border-radius: 10px;
    font-size: 1.8rem;
    border: 1px solid #ffffff29;
    background: #00000063;
    color: #d0d0d0;
    font-weight: 500;
    cursor: pointer;
    font-family: 'Fredoka';
}

button:hover {
    background: #000000c5;
}

.play-btn {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
}

.quiz-container {
    margin: 10px;
    width: 100%;
    min-width: 650px;
    color: white;
    height: 50rem;
    transition: .3s;
    position: relative;
    border-radius: 2rem;
    border: 1px solid #626262c2;
    background: radial-gradient(#303035, #080808 74%);
        transform: scale(0.7);
}

.instructions {
    z-index: 1;
    width: 100%;
    height: 100%;
    display: flex;
    padding: 0 1rem;
    transition: .3s;
    position: absolute;
    border-radius: 2rem;
    flex-direction: column;
}

.instructions ul {
    font-size: 1.7em;
    color: #eaeaea;
    font-weight: 100;
    font-style: italic;
    padding: 1rem 0 1rem 1rem;
}

.instructions li {
    margin: 2rem;
    list-style: auto;
    border-bottom: 1px solid rgb(255 255 255 / 4%);
}

.top {
    width: 100%;
    display: flex;
    color: #c6c6c6;
    font-size: 2.2rem;
    padding: 1rem 2rem;
    position: relative;
    font-family: 'Fredoka';
    justify-content: space-between;
    box-shadow: 0px 8px 8px 1px #0000007a;
}

.start-quiz {
    align-self: center;
}

.game {
    width: 100%;
    height: 100%;
    border-radius: 15px;
    transition: opacity .3s;
    flex-direction: column;
}

.level,
.score {
    font-family: 'Fredoka';
}

.level span,
.score span {
    margin-left: 5px;
}

.timer {
    left: 0;
    bottom: 0;
    height: 5%;
    position: absolute;
    background: #8ba5ee;
}

.timer-animation {
    animation: timer 10s linear forwards;
}

.body {
    width: 100%;
    padding: 2rem;
    display: flex;
    transition: .3s;
    font-size: 2.2rem;
    flex-direction: column;
    justify-content: center;
    height: -webkit-fill-available;
}

.ques-container {
    width: 100%;
    flex-direction: column;
    filter: drop-shadow(0px 0px 1px #AfAfAf);
}

.ques-number {
    width: 20%;
    color: #d4d4d4;
    text-align: center;
    margin-bottom: -1%;
    background: #060606;
    border-radius: 10rem 10rem 0 0;
    clip-path: polygon(10% 0%, 90% 0, 100% 100%, 0% 100%);
}

.question {
    min-height: 8rem;
    width: 100%;
    color: #d4d4d4;
    text-align: center;
    border-radius: 50px;
    background: #060606;
    padding: 1.2rem 2.5rem 0.5rem;
    clip-path: polygon(4% 5%, 96% 5%, 100% 4%, 95% 100%, 0% 100%, 5% 100%, 0% 4%);
}

.options {
    padding: 2.5rem 0;
    flex-wrap: wrap;
    grid-gap: 1.2rem;
}

.options li {
    width: 90%;
    cursor: pointer;
    font-size: 2rem;
    color: #9b9b9b;
    padding: 8px 20px;
    font-style: italic;
    background: #060606;
    border-radius: 50px 10px;
    border: 1px solid #4a4a4a;
    list-style: inside upper-alpha;
}

.options li:hover {
    background: #161616;
}

.bottom {
    width: 100%;
    color: #c2c2c2;
    font-size: 1.8rem;
    padding: 1rem 2rem;
    border-radius: 0 0 15px;
    justify-content: space-between;
    box-shadow: 0px -4px 6px 0px #00000038;
}

.result {
    width: 80%;
    height: 60%;
    transition: .1s;
    color: #c5c5c5;
    font-size: 2.2rem;
    padding: 1.5rem 0;
    position: absolute;
    border-radius: 40px;
    flex-direction: column;
    border: 1px solid #ffffff40;
    box-shadow: 0px 0px 20px 4px #00000099;
}

.details {
    height: -webkit-fill-available;
    display: flex;
    place-items: center;
}

table {
    border-radius: 10px;
    border: 1px solid rgb(52, 52, 52);
    color: rgb(210, 210, 210);
}

.action {
    display: flex;
    justify-content: space-between;
    width: 100%;
    padding: 0 30px;
}

th,
td {
    background: #08080829;
    text-align: center;
    padding: 10px;
    font-size: 2rem;
}

.final-statement {
    color: #aaffaa;
    font-style: italic;
    margin: -2rem 0 3rem;
    font-weight: bold;
    font-size: 2rem;
}

@keyframes timer {
    0% {
        width: 0%;
    }

    100% {
        width: 100%;
    }

}
  • Fonts and Layout: Uses Google Fonts (Fredoka) and flexbox throughout for centered, responsive design.
  • Hidden Class: Manages visibility using opacity and visibility—great for screen transitions without removing elements from the DOM.
  • Hover Effects: Subtle UI interactions, like darkening the background on hover for buttons and options.
  • Clip-path and border-radius: Used creatively to give rounded and angled card-style containers to the question and options.
  • Timer Bar: The timer at the bottom uses CSS keyframes (@keyframes timer) and transitions to visually indicate the countdown.

Visual Feedback on Answer Selection:

  • Correct options are marked in green using .correctAnswer
  • Incorrect ones are marked in red using .incrorrectAnswer

This improves user experience and clearly shows right/wrong answers in our multi-level quiz app.

JavaScript Functionality (Game Logic)

JavaScript is where all the interactivity and logic happens in this JavaScript multi-level quiz app project.

const questions = [
    {
        question: "What CSS property is used to add shadow to an element's text?",
        options: ["box-shadow", "text-shadow", "box-text", "element-shadow"],
        correctAnswer: "text-shadow"
    },

    {
        question: "Which JavaScript function is used to round a number to the nearest integer?",
        options: ["ceil()", "round()", "floor()", "random()"],
        correctAnswer: "round()"
    },

    {
        question: "Which JavaScript method is used to remove the first element from an array?",
        options: ["remove()", "pop()", "splice()", "shift()"],
        correctAnswer: "shift()"
    },

    {
        question: "In JavaScript, what is the purpose of the `JSON.parse()` method?",
        options: ["To parse XML data", "To parse JSON data", "To parse HTML data", "To create a new function"],
        correctAnswer: "To parse JSON data"
    },

    {
        question: "What does CSS `float: left;` property value do?",
        options: ["Floats the element to the right", "Floats the element to the center", "Floats the element to the left", "Does not affect element positioning"],
        correctAnswer: "Floats the element to the left"
    }
]
 
const quizContainer = document.querySelector('.quiz-container');
const gameContainer = document.querySelector('.game');
const timer = document.querySelector('.timer');
const quesNum = document.querySelector('.ques-number');
const questionBox = document.querySelector('.question');
const optionsContainer = document.querySelector('.options')
const optionsBox = Array.from(document.querySelectorAll('.options li'));
const nextQuesBtn = document.querySelector('.next-btn')
const playBtn = document.querySelector('.play-btn')
const startQuizBtn = document.querySelector('.start-quiz')
const instructionsBox = document.querySelector('.instructions')
const QcountBox = document.querySelector('.question-count span');
const topScoreBox = document.querySelector('.score span');
const resultBox = document.querySelector('.result');
const scoreCard = Array.from(document.querySelectorAll('.scorecard td'))
const finalStatement = document.querySelectorAll('.final-statement span')
const exitBtn = document.querySelector('.exit');
const nextLevelBtn = document.querySelector('.next-level');
const levelBox = document.querySelector('.level span');
let currentQIndex = 0;
let correctOptionIndex;
let quesNumCounter = 1;
let nextBtnTimeout;
let totalQuestions = questions.length;
let score = 0;
let levels = [5,10,15];   
let totalLevels = levels.length;
let levelIndex = 0;
let currentLevel;
let quesPerLevel;
let lastQuesIndex = -1;

function evalQuesPerLevel() {
    quesNumCounter = 1;
    nextQuesBtn.textContent = 'Next';
    quesPerLevel = levels[levelIndex]
    lastQuesIndex = lastQuesIndex + quesPerLevel;
    currentLevel = levelIndex + 1;
    levelIndex++;
}

function loadQuestion() {
    if (currentQIndex == lastQuesIndex) {
        nextQuesBtn.textContent = 'Show Result';
    }

    if (currentQIndex <= lastQuesIndex) {
        optionsContainer.style.pointerEvents = 'auto';
        nextQuesBtn.disabled = true;
        nextQuesBtn.classList.add('hidden');
        quesNum.textContent = quesNumCounter;
        QcountBox.textContent = `${quesNumCounter} of ${quesPerLevel}`;
        levelBox.textContent = currentLevel;
        timer.classList.remove('timer-animation');
        startTimer();
        const ques = questions[currentQIndex].question;
        questionBox.textContent = ques;
        const optionsText = questions[currentQIndex].options;

        optionsText.forEach((op, i) => {
            optionsBox[i].textContent = op;
            optionsBox[i].classList.remove('correctAnswer')
            optionsBox[i].classList.remove('incrorrectAnswer')

            if (op == questions[currentQIndex].correctAnswer) {
                correctOptionIndex = i;
            }
        })
        quesNumCounter++;
    }

    else {
        gameContainer.classList.add('hidden')
        showResult()
    }
}

function startTimer() {
    setTimeout(() => timer.classList.add('timer-animation'), 0)
    timer.style.animationPlayState = 'running';
    nextBtnTimeout = setTimeout(() => {
        nextQuesBtn.disabled = false
        optionsContainer.style.pointerEvents = 'none';
        autoSelected()
        scoreCard[2].textContent++;
        currentQIndex++;
        nextQuesBtn.classList.remove('hidden')
    }, 10000)
}

function skipTimer() {
    timer.style.animationPlayState = 'paused';
    nextQuesBtn.disabled = false;
    clearTimeout(nextBtnTimeout)
}

function selectedOption(event) {
    skipTimer();
    optionsContainer.style.pointerEvents = 'none';
    scoreCard[1].textContent++;
    nextQuesBtn.classList.remove('hidden')

    if (event.target.textContent == questions[currentQIndex].correctAnswer) {
        event.target.classList.add('correctAnswer')
        topScoreBox.textContent = ++score;
        scoreCard[3].textContent++;
    }
    else {
        event.target.classList.add('incrorrectAnswer')
        scoreCard[4].textContent++;
        autoSelected();
    }
    currentQIndex++;
}

function autoSelected() {
    optionsBox[correctOptionIndex].classList.add('correctAnswer')
}

function startGame() {
    scoreCard.forEach(element => element.textContent = 0)
    score = 0;
    topScoreBox.textContent = score;
    evalQuesPerLevel();
    loadQuestion()
    startQuizBtn.classList.add('hidden')
    instructionsBox.classList.add('hidden')
    gameContainer.classList.toggle('hidden')
    resultBox.classList.add('hidden');
}

function showInstructions() {
    playBtn.classList.add('hidden')
    quizContainer.classList.remove('hidden');
    instructionsBox.classList.remove('hidden');
}

function showResult() {
    nextLevelBtn.classList.remove('hidden');
    scoreCard[0].textContent = quesPerLevel;
    resultBox.classList.remove('hidden');
    finalStatement[0].textContent = ' ' + score;
    finalStatement[1].textContent = ' ' + quesPerLevel;
    if (currentQIndex == totalQuestions) {
        nextLevelBtn.disabled = true;
        nextLevelBtn.classList.add('hidden');
    }
}

function reset() {
    currentQIndex = 0;
    score = 0;
    levelIndex = 0;
    lastQuesIndex = -1;
    nextLevelBtn.disabled = false;
}
function exitQuiz() {
    reset();
    resultBox.classList.add('hidden');
    playBtn.classList.remove('hidden');
    startQuizBtn.classList.remove('hidden');
    quizContainer.classList.add('hidden');

}

nextQuesBtn.addEventListener('click', loadQuestion)
playBtn.addEventListener('click', showInstructions)
optionsBox.forEach((elem) => elem.addEventListener('click', selectedOption))
startQuizBtn.addEventListener('click', startGame)
exitBtn.addEventListener('click', exitQuiz)
nextLevelBtn.addEventListener('click', startGame)

Question Loading: The multi-level quiz app uses an array of question objects, each containing:

  • question
  • options
  • correctAnswer

The loadQuestion() function populates the DOM with current question and options dynamically.

Timer Mechanism: Each question has a 10-second timer:

  • A setTimeout() is used to show the “Next” button if no answer is selected.
  • A CSS animation (timer-animation) shows the visual timer filling from 0% to 100%.
  • If time runs out, the question is auto-marked as unanswered.

Answer Selection: When a user clicks an option:

  • The app checks whether the answer is correct.
  • Adds score if right, marks the box green.
  • If wrong, marks it red and shows the correct answer.

Multi-Level System: The multi-level quiz app supports multiple levels (default: 5 questions per level). After each level:

  • A result table shows how many questions were answered, skipped, right or wrong.
  • Score is preserved across levels.
  • “Next Level” button starts the next set of questions.

After the last level, the final score and breakdown are displayed. The app also includes:

  • A “Play Again” logic using reset()
  • An “Exit Quiz” button to go back to the initial screen.

Final Thoughts

This interactive multi-level quiz app using JavaScript not only looks professional but teaches key front-end skills:

  • Dynamic DOM manipulation
  • Event-driven design
  • Timer control with animations
  • Quiz scoring logic
  • Responsive layouts and animations

It’s perfect for those learning JavaScript or wanting to build a real-world project using only HTML, CSS and JavaScript.

Leave a Comment

Scroll to Top