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.
https://basescripts.com/javascript-quiz-project-how-to-create-a-quiz-with-javascript-html-css-json
What you will buildLoads 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 structurequiz-project/ index.html styles.css app.js questions.json images/ q1.jpg q2.jpg q3.jpg
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
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)
JavaScript Quiz Quiz Loading… Previous Next Submit3) 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 {
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 {
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 = `
Quiz Complete
You scored ${score} out of ${total}.
${quiz.questions.map((q, i) => {
const isCorrect = selected[i] === q.correctIndex;
return `
${isCorrect ? "✅" : "❌"} Question ${i + 1}
`;
}).join("")}
Restart
`;
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