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.jsglobal 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