JavaScript Quiz Project How to Create a Quiz with JavaScript (HTML + CSS + JSON)

https://github.com/lsvekis/JavaScript-Projects

How to Create a Quiz with JavaScript (HTML + CSS + JSON)

This tutorial shows you how to build a simple quiz app using modern JavaScript. The quiz loads questions from a JSON file, lets users pick answers, tracks progress, and displays a final score.

What you will build

  • Loads questions from questions.json
  • Displays one question at a time
  • Highlights the selected answer
  • Next/Previous navigation
  • Progress bar and answered count
  • Final score screen

Project structure

quiz-project/
  index.html
  styles.css
  app.js
  questions.json
  images/
    q1.jpg
    q2.jpg
    q3.jpg

Step 1: Create the JSON question file

Create a file named questions.json. This is where your quiz data lives.

{
  "title": "JavaScript Quiz",
  "questions": [
    {
      "id": 1,
      "question": "Which keyword declares a block-scoped variable?",
      "image": "images/q1.jpg",
      "choices": ["var", "let", "function", "const"],
      "correctIndex": 1
    }
  ]
}

Key idea: each question has an array of choices and the correct answer is stored as correctIndex (0-based).


Step 2: Create the HTML layout

The HTML is minimal: a title, question text, image, answer buttons, navigation, and progress.

<div class="choices" id="choices"></div>

<button id="prevBtn">Previous</button>
<button id="nextBtn">Next</button>
<button id="submitBtn">Submit</button>

We generate the answer buttons dynamically in JavaScript so the quiz can support any questions you add to the JSON.


Step 3: Add simple CSS

Keep styling simple so the focus stays on learning JavaScript and DOM manipulation:

.choice {
  border: 1px solid #ccc;
  background: #f0f0f0;
  padding: 10px;
  border-radius: 6px;
  cursor: pointer;
}

.choice.selected {
  background: #d8ebff;
  border-color: #2b7de9;
}

Step 4: Load JSON using fetch()

Modern projects load quiz data with fetch():

const res = await fetch("./questions.json");
quiz = await res.json();

This makes it easy to update the quiz without editing your JavaScript logic.


Step 5: Render questions and choices

We render the current question, then loop over the choices to create buttons:

choicesEl.innerHTML = "";
q.choices.forEach((text, idx) => {
  const btn = document.createElement("button");
  btn.className = "choice";
  btn.textContent = text;
  btn.addEventListener("click", () => choose(idx));
  choicesEl.appendChild(btn);
});

When a choice is clicked we save the answer and re-render so the selected option is highlighted.


Step 6: Score the quiz

When the user submits, compare selected answers to correctIndex:

let score = 0;
quiz.questions.forEach((q, i) => {
  if (selected[i] === q.correctIndex) score++;
});

Important note: Running locally

If you open the HTML file by double-clicking it, some browsers block fetch(). Run a simple local server:

python3 -m http.server 5500

Then open: http://localhost:5500


Next upgrades (easy ideas)

  • Shuffle questions and choices
  • Show correct answer after each question
  • Add a timer
  • Add categories and difficulty levels in JSON
  • Save progress using localStorage

If you want, I can extend this quiz to include any of those upgrades while keeping the code beginner-friendly.

Below is a modernized quiz project with:

  • ✅ Real JSON file (no more json.js global variable)
  • ✅ Clean HTML + CSS (simple)
  • ✅ Modern JavaScript (ES modules) with fetch()
  • ✅ Better structure: state, rendering, scoring, progress
  • ✅ A blog post (HTML) you can paste into WordPress (includes full code)

1) questions.json (updated JSON)

Create a file named questions.json in the same folder as your HTML:

{
"title": "JavaScript Quiz",
"questions": [
{
"id": 1,
"question": "Which keyword declares a block-scoped variable?",
"image": "images/q1.jpg",
"choices": ["var", "let", "function", "const"],
"correctIndex": 1
},
{
"id": 2,
"question": "What does DOM stand for?",
"image": "images/q2.jpg",
"choices": ["Data Object Model", "Document Object Model", "Digital Order Map", "Document Order Map"],
"correctIndex": 1
},
{
"id": 3,
"question": "Which method selects the first matching element?",
"image": "images/q3.jpg",
"choices": ["querySelector()", "querySelectorAll()", "getElementsByClassName()", "getElement()"],
"correctIndex": 0
}
]
}

2) index.html (modern HTML)

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>JavaScript Quiz</title>
<link rel="stylesheet" href="styles.css" />
</head><body>
<main class="wrap">
<header class="header">
<h1 id="quizTitle">Quiz</h1>
<p class="meta" id="quizMeta"></p>
</header> <section class="card" id="app">
<h2 class="question" id="questionText">Loading…</h2> <img class="image" id="questionImage" alt="" /> <div class="choices" id="choices"></div> <div class="controls">
<button class="btn" id="prevBtn" type="button">Previous</button>
<button class="btn" id="nextBtn" type="button">Next</button>
<button class="btn primary" id="submitBtn" type="button">Submit</button>
</div> <div class="progress">
<div class="bar" id="progressBar"></div>
</div>
<p class="meta" id="progressText"></p>
</section>
</main> <script type="module" src="app.js"></script>
</body>
</html>

3) styles.css (simple styling)

body {
font-family: Arial, sans-serif;
background: #f5f5f5;
margin: 0;
padding: 20px;
color: #222;
}.wrap {
max-width: 760px;
margin: 0 auto;
}.header {
margin-bottom: 14px;
}h1 {
margin: 0 0 6px;
}.meta {
margin: 0;
color: #666;
font-size: 14px;
}.card {
background: #fff;
border: 1px solid #ddd;
border-radius: 8px;
padding: 18px;
}.question {
margin: 0 0 12px;
}.image {
width: 100%;
max-height: 260px;
object-fit: cover;
border-radius: 6px;
border: 1px solid #eee;
display: none;
margin-bottom: 12px;
}.choices {
display: grid;
gap: 10px;
margin: 12px 0 14px;
}.choice {
border: 1px solid #ccc;
background: #f0f0f0;
padding: 10px;
border-radius: 6px;
cursor: pointer;
text-align: left;
}.choice.selected {
background: #d8ebff;
border-color: #2b7de9;
}.controls {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-bottom: 12px;
}.btn {
padding: 8px 14px;
border-radius: 6px;
border: 1px solid #bbb;
background: #eee;
cursor: pointer;
}.btn.primary {
background: #2b7de9;
border-color: #2b7de9;
color: #fff;
}.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}.progress {
height: 10px;
background: #e3e3e3;
border-radius: 99px;
overflow: hidden;
}.bar {
height: 10px;
width: 0%;
background: #2b7de9;
transition: width 0.2s ease;
}

4) app.js (modern JavaScript)

const quizTitleEl = document.getElementById("quizTitle");
const quizMetaEl = document.getElementById("quizMeta");const questionTextEl = document.getElementById("questionText");
const questionImageEl = document.getElementById("questionImage");
const choicesEl = document.getElementById("choices");const prevBtn = document.getElementById("prevBtn");
const nextBtn = document.getElementById("nextBtn");
const submitBtn = document.getElementById("submitBtn");const progressBarEl = document.getElementById("progressBar");
const progressTextEl = document.getElementById("progressText");let quiz = null;
let currentIndex = 0;
let selected = []; // stores selected choice index per question (number or null)init();async function init() {
try {
const res = await fetch("./questions.json", { cache: "no-store" });
if (!res.ok) throw new Error("Could not load questions.json");
quiz = await res.json(); selected = new Array(quiz.questions.length).fill(null); hookEvents();
render();
} catch (err) {
questionTextEl.textContent = "Error loading quiz data.";
progressTextEl.textContent = String(err.message || err);
}
}function hookEvents() {
prevBtn.addEventListener("click", () => {
if (currentIndex > 0) {
currentIndex--;
render();
}
}); nextBtn.addEventListener("click", () => {
if (selected[currentIndex] === null) {
alert("Please choose an answer first.");
return;
}
if (currentIndex < quiz.questions.length - 1) {
currentIndex++;
render();
}
}); submitBtn.addEventListener("click", () => {
if (selected[currentIndex] === null) {
alert("Please answer the last question.");
return;
}
showResults();
}); // Optional keyboard shortcuts (1-4)
document.addEventListener("keydown", (e) => {
const n = Number(e.key);
if (n >= 1 && n <= 4) {
choose(n - 1);
}
});
}function render() {
const total = quiz.questions.length;
const q = quiz.questions[currentIndex]; quizTitleEl.textContent = quiz.title || "Quiz";
quizMetaEl.textContent = `Question ${currentIndex + 1} of ${total}`; questionTextEl.textContent = q.question; // image (optional)
if (q.image) {
questionImageEl.src = q.image;
questionImageEl.alt = `Question ${currentIndex + 1} image`;
questionImageEl.style.display = "block";
} else {
questionImageEl.removeAttribute("src");
questionImageEl.alt = "";
questionImageEl.style.display = "none";
} // choices
choicesEl.innerHTML = "";
q.choices.forEach((text, idx) => {
const btn = document.createElement("button");
btn.type = "button";
btn.className = "choice" + (selected[currentIndex] === idx ? " selected" : "");
btn.textContent = text;
btn.addEventListener("click", () => choose(idx));
choicesEl.appendChild(btn);
}); // controls
prevBtn.disabled = currentIndex === 0; const onLast = currentIndex === total - 1;
nextBtn.style.display = onLast ? "none" : "inline-block";
submitBtn.style.display = onLast ? "inline-block" : "none";
submitBtn.disabled = selected[currentIndex] === null; updateProgress();
}function choose(choiceIndex) {
selected[currentIndex] = choiceIndex;
render();
}function updateProgress() {
const total = quiz.questions.length;
const answered = selected.filter(v => v !== null).length;
const percent = Math.round((answered / total) * 100); progressBarEl.style.width = percent + "%";
progressTextEl.textContent = `${answered}/${total} answered (${percent}%)`;
}function showResults() {
const total = quiz.questions.length; let score = 0;
quiz.questions.forEach((q, i) => {
if (selected[i] === q.correctIndex) score++;
}); const resultsHtml = `
<h2>Quiz Complete</h2>
<p>You scored <strong>${score}</strong> out of <strong>${total}</strong>.</p>
<ol>
${quiz.questions.map((q, i) => {
const isCorrect = selected[i] === q.correctIndex;
return `<li>
${isCorrect ? "✅" : "❌"} Question ${i + 1}
</li>`;
}).join("")}
</ol>
<button class="btn" id="restartBtn" type="button">Restart</button>
`; document.getElementById("app").innerHTML = resultsHtml;
document.getElementById("restartBtn").addEventListener("click", () => location.reload());
}

Important note (so fetch() works)

If you double-click index.html, some browsers block fetch() for local files.

Run a quick local server instead:

  • VS Code → Live Server
  • or terminal (in the project folder):
python3 -m http.server 5500

Then open: http://localhost:5500