UI Components Medium
Quiz Widget
An interactive quiz component with real-time scoring, progress tracking, and result summaries.
Open in Lab
MCP
vanilla-js css react tailwind vue svelte
Targets: TS JS HTML React Vue Svelte
Code
:root {
--quiz-bg: rgba(255, 255, 255, 0.04);
--quiz-border: rgba(255, 255, 255, 0.08);
--quiz-primary: #6366f1;
--quiz-primary-glow: rgba(99, 102, 241, 0.25);
--quiz-correct: #22c55e;
--quiz-correct-bg: rgba(34, 197, 94, 0.12);
--quiz-incorrect: #ef4444;
--quiz-incorrect-bg: rgba(239, 68, 68, 0.12);
--quiz-text: #f8fafc;
--quiz-muted: #94a3b8;
--quiz-surface: rgba(255, 255, 255, 0.03);
--quiz-btn-border: rgba(255, 255, 255, 0.1);
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
min-height: 100vh;
display: grid;
place-items: center;
background: #0b1221;
padding: 1rem;
}
.quiz-container {
background: var(--quiz-bg);
border: 1px solid var(--quiz-border);
border-radius: 20px;
padding: 2rem;
max-width: 520px;
margin: 0 auto;
font-family: "Inter", system-ui, sans-serif;
backdrop-filter: blur(16px);
}
.quiz-progress-wrapper {
width: 100%;
height: 4px;
background: rgba(255, 255, 255, 0.08);
border-radius: 4px;
overflow: hidden;
margin-bottom: 1.25rem;
}
.quiz-progress {
height: 100%;
background: linear-gradient(90deg, var(--quiz-primary), #818cf8);
width: 0%;
transition: width 0.5s cubic-bezier(0.4, 0, 0.2, 1);
border-radius: 4px;
box-shadow: 0 0 12px var(--quiz-primary-glow);
}
.quiz-meta {
display: flex;
justify-content: space-between;
font-size: 0.813rem;
font-weight: 600;
color: var(--quiz-muted);
margin-bottom: 1.75rem;
letter-spacing: 0.025em;
}
#question-text {
font-size: 1.2rem;
font-weight: 700;
color: var(--quiz-text);
margin-bottom: 1.5rem;
line-height: 1.5;
}
.btn-grid {
display: flex;
flex-direction: column;
gap: 0.625rem;
}
.quiz-btn {
background: var(--quiz-surface);
border: 1px solid var(--quiz-btn-border);
border-radius: 12px;
padding: 0.9rem 1.25rem;
font-size: 0.938rem;
font-weight: 500;
text-align: left;
cursor: pointer;
transition: all 0.2s;
color: var(--quiz-muted);
letter-spacing: 0.01em;
}
.quiz-btn:hover:not(:disabled) {
background: rgba(99, 102, 241, 0.12);
border-color: rgba(99, 102, 241, 0.4);
color: var(--quiz-text);
transform: translateX(2px);
}
.quiz-btn.correct {
background: var(--quiz-correct-bg);
border-color: var(--quiz-correct);
color: #4ade80;
box-shadow: 0 0 16px rgba(34, 197, 94, 0.15);
}
.quiz-btn.incorrect {
background: var(--quiz-incorrect-bg);
border-color: var(--quiz-incorrect);
color: #f87171;
box-shadow: 0 0 16px rgba(239, 68, 68, 0.15);
}
.quiz-btn:disabled {
cursor: default;
transform: none;
}
.result-screen {
text-align: center;
padding: 1rem 0;
}
.result-screen h2 {
font-size: 1.75rem;
font-weight: 800;
color: var(--quiz-text);
margin-bottom: 0.75rem;
}
.result-screen p {
color: var(--quiz-muted);
font-size: 1rem;
margin-bottom: 1.5rem;
}
.score-display {
display: inline-flex;
align-items: center;
justify-content: center;
width: 80px;
height: 80px;
border-radius: 50%;
border: 3px solid var(--quiz-primary);
font-size: 1.5rem;
font-weight: 800;
color: var(--quiz-text);
margin: 1rem auto;
box-shadow: 0 0 32px var(--quiz-primary-glow);
background: rgba(99, 102, 241, 0.1);
}
.restart-btn {
background: linear-gradient(135deg, var(--quiz-primary), #818cf8);
color: white;
border: none;
padding: 0.75rem 2.25rem;
border-radius: 12px;
font-weight: 700;
font-size: 0.938rem;
cursor: pointer;
margin-top: 0.5rem;
transition: all 0.2s;
box-shadow: 0 0 24px var(--quiz-primary-glow);
}
.restart-btn:hover {
opacity: 0.9;
transform: translateY(-2px);
box-shadow: 0 8px 32px var(--quiz-primary-glow);
}const questions = [
{
question: "Which CSS property is used to create a flex container?",
answers: [
{ text: "display: grid", correct: false },
{ text: "display: block", correct: false },
{ text: "display: flex", correct: true },
{ text: "display: table", correct: false },
],
},
{
question: "What does HTML stand for?",
answers: [
{ text: "Hyper Text Markup Language", correct: true },
{ text: "Home Tool Markup Language", correct: false },
{ text: "Hyperlinks and Text Markup Language", correct: false },
{ text: "Hyper Text Modern Language", correct: false },
],
},
{
question: "Which company originally developed JavaScript?",
answers: [
{ text: "Microsoft", correct: false },
{ text: "Netscape", correct: true },
{ text: "Google", correct: false },
{ text: "IBM", correct: false },
],
},
{
question: "Which CSS unit is relative to the root element font size?",
answers: [
{ text: "em", correct: false },
{ text: "px", correct: false },
{ text: "rem", correct: true },
{ text: "vh", correct: false },
],
},
{
question: "What is the correct way to declare a variable in modern JavaScript?",
answers: [
{ text: "var x = 5", correct: false },
{ text: "const x = 5", correct: true },
{ text: "int x = 5", correct: false },
{ text: "declare x = 5", correct: false },
],
},
];
let currentQuestionIndex = 0;
let score = 0;
const questionEl = document.getElementById("question-text");
const answerButtonsEl = document.getElementById("answer-buttons");
const progressEl = document.getElementById("quiz-progress");
const scoreEl = document.getElementById("quiz-score");
const qNumEl = document.getElementById("question-number");
const resultScreen = document.getElementById("result-screen");
const quizBody = document.querySelector(".quiz-body");
const finalScoreText = document.getElementById("final-score-text");
const restartBtn = document.getElementById("restart-btn");
function startQuiz() {
currentQuestionIndex = 0;
score = 0;
resultScreen.style.display = "none";
quizBody.style.display = "block";
updateScore();
showQuestion(questions[currentQuestionIndex]);
}
function showQuestion(question) {
questionEl.innerText = question.question;
answerButtonsEl.innerHTML = "";
qNumEl.innerText = `Question ${currentQuestionIndex + 1}/${questions.length}`;
progressEl.style.width = `${(currentQuestionIndex / questions.length) * 100}%`;
question.answers.forEach((answer) => {
const button = document.createElement("button");
button.innerText = answer.text;
button.classList.add("quiz-btn");
if (answer.correct) button.dataset.correct = "true";
button.addEventListener("click", selectAnswer);
answerButtonsEl.appendChild(button);
});
}
function selectAnswer(e) {
const selectedBtn = e.target;
const isCorrect = selectedBtn.dataset.correct === "true";
if (isCorrect) {
selectedBtn.classList.add("correct");
score++;
} else {
selectedBtn.classList.add("incorrect");
Array.from(answerButtonsEl.children).forEach((btn) => {
if (btn.dataset.correct === "true") btn.classList.add("correct");
});
}
Array.from(answerButtonsEl.children).forEach((btn) => (btn.disabled = true));
updateScore();
setTimeout(() => {
currentQuestionIndex++;
if (questions.length > currentQuestionIndex) {
showQuestion(questions[currentQuestionIndex]);
} else {
showResults();
}
}, 1400);
}
function updateScore() {
scoreEl.innerText = `Score: ${score}`;
}
function showResults() {
quizBody.style.display = "none";
resultScreen.style.display = "block";
progressEl.style.width = "100%";
const pct = Math.round((score / questions.length) * 100);
let emoji = pct === 100 ? "🏆" : pct >= 60 ? "🎯" : "📚";
// Inject score circle if it doesn't exist
let scoreCircle = resultScreen.querySelector(".score-display");
if (!scoreCircle) {
scoreCircle = document.createElement("div");
scoreCircle.className = "score-display";
resultScreen.insertBefore(scoreCircle, resultScreen.querySelector("p"));
}
scoreCircle.textContent = `${score}/${questions.length}`;
if (finalScoreText) {
finalScoreText.innerText = `${emoji} You scored ${pct}% — ${score} out of ${questions.length} correct!`;
}
}
restartBtn.addEventListener("click", startQuiz);
startQuiz();<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Quiz Widget</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="quiz-container" id="quiz">
<div id="quiz-header">
<div class="quiz-progress-wrapper">
<div id="quiz-progress" class="quiz-progress"></div>
</div>
<div class="quiz-meta">
<span id="question-number">Question 1/5</span>
<span id="quiz-score">Score: 0</span>
</div>
</div>
<div class="quiz-body">
<h2 id="question-text">Loading question...</h2>
<div id="answer-buttons" class="btn-grid">
<!-- Buttons will be generated by JS -->
</div>
</div>
<div id="result-screen" class="result-screen" style="display: none;">
<h2>Quiz Complete!</h2>
<p id="final-score-text"></p>
<button id="restart-btn" class="restart-btn">Try Again</button>
</div>
</div>
<script src="script.js"></script>
</body>
</html>import { useState } from "react";
const QUESTIONS = [
{
question: "Which CSS property creates a flex container?",
answers: ["display: grid", "display: block", "display: flex", "display: table"],
correct: 2,
},
{
question: "What does HTML stand for?",
answers: [
"Hyper Text Markup Language",
"Home Tool Markup Language",
"Hyperlinks and Text Markup Language",
"Hyper Text Modern Language",
],
correct: 0,
},
{
question: "Which company originally developed JavaScript?",
answers: ["Microsoft", "Netscape", "Google", "IBM"],
correct: 1,
},
{
question: "Which CSS unit is relative to the root element font size?",
answers: ["em", "px", "rem", "vh"],
correct: 2,
},
{
question: "The correct way to declare a variable in modern JavaScript?",
answers: ["var x = 5", "const x = 5", "int x = 5", "declare x = 5"],
correct: 1,
},
];
export default function QuizWidgetRC() {
const [idx, setIdx] = useState(0);
const [score, setScore] = useState(0);
const [selected, setSelected] = useState<number | null>(null);
const [done, setDone] = useState(false);
const q = QUESTIONS[idx];
const pct = Math.round((score / QUESTIONS.length) * 100);
function pick(i: number) {
if (selected !== null) return;
setSelected(i);
if (i === q.correct) setScore((s) => s + 1);
setTimeout(() => {
if (idx + 1 < QUESTIONS.length) {
setIdx((n) => n + 1);
setSelected(null);
} else setDone(true);
}, 1200);
}
function restart() {
setIdx(0);
setScore(0);
setSelected(null);
setDone(false);
}
function btnStyle(i: number): React.CSSProperties {
if (selected === null)
return { background: "#21262d", borderColor: "#30363d", color: "#e6edf3" };
if (i === q.correct)
return {
background: "rgba(35,134,54,0.3)",
borderColor: "rgba(126,231,135,0.5)",
color: "#7ee787",
};
if (i === selected)
return {
background: "rgba(248,81,73,0.2)",
borderColor: "rgba(248,81,73,0.5)",
color: "#f85149",
};
return { background: "#21262d", borderColor: "#30363d", color: "#484f58" };
}
if (done) {
return (
<div className="min-h-screen bg-[#0d1117] flex items-center justify-center p-6">
<div className="bg-[#161b22] border border-[#30363d] rounded-2xl p-8 text-center w-full max-w-sm">
<p className="text-5xl mb-4">{pct === 100 ? "🏆" : pct >= 60 ? "🎯" : "📚"}</p>
<h2 className="text-[#e6edf3] text-2xl font-bold mb-1">
{score}/{QUESTIONS.length}
</h2>
<p className="text-[#8b949e] text-sm mb-6">You scored {pct}%</p>
<div className="h-2 bg-[#21262d] rounded-full mb-6">
<div
className="h-full bg-[#58a6ff] rounded-full transition-all"
style={{ width: `${pct}%` }}
/>
</div>
<button
onClick={restart}
className="w-full py-2.5 bg-[#238636] border border-[#2ea043] text-white rounded-xl font-semibold text-sm hover:bg-[#2ea043] transition-colors"
>
Try Again
</button>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-[#0d1117] flex items-center justify-center p-6">
<div className="bg-[#161b22] border border-[#30363d] rounded-2xl p-6 w-full max-w-sm">
<div className="flex items-center justify-between mb-4">
<span className="text-xs text-[#8b949e]">
Question {idx + 1}/{QUESTIONS.length}
</span>
<span className="text-xs font-semibold text-[#58a6ff]">Score: {score}</span>
</div>
<div className="h-1 bg-[#21262d] rounded-full mb-5">
<div
className="h-full bg-[#58a6ff] rounded-full transition-all"
style={{ width: `${(idx / QUESTIONS.length) * 100}%` }}
/>
</div>
<p className="text-[#e6edf3] font-semibold text-base mb-4 leading-snug">{q.question}</p>
<div className="space-y-2">
{q.answers.map((a, i) => (
<button
key={i}
onClick={() => pick(i)}
disabled={selected !== null}
className="w-full text-left px-4 py-2.5 rounded-xl border text-sm transition-all duration-200"
style={btnStyle(i)}
>
{a}
</button>
))}
</div>
</div>
</div>
);
}<script setup>
import { ref, computed } from "vue";
const QUESTIONS = [
{
question: "Which CSS property creates a flex container?",
answers: ["display: grid", "display: block", "display: flex", "display: table"],
correct: 2,
},
{
question: "What does HTML stand for?",
answers: [
"Hyper Text Markup Language",
"Home Tool Markup Language",
"Hyperlinks and Text Markup Language",
"Hyper Text Modern Language",
],
correct: 0,
},
{
question: "Which company originally developed JavaScript?",
answers: ["Microsoft", "Netscape", "Google", "IBM"],
correct: 1,
},
{
question: "Which CSS unit is relative to the root element font size?",
answers: ["em", "px", "rem", "vh"],
correct: 2,
},
{
question: "The correct way to declare a variable in modern JavaScript?",
answers: ["var x = 5", "const x = 5", "int x = 5", "declare x = 5"],
correct: 1,
},
];
const idx = ref(0);
const score = ref(0);
const selected = ref(null);
const done = ref(false);
const q = computed(() => QUESTIONS[idx.value]);
const pct = computed(() => Math.round((score.value / QUESTIONS.length) * 100));
const progressWidth = computed(() => (idx.value / QUESTIONS.length) * 100);
function pick(i) {
if (selected.value !== null) return;
selected.value = i;
if (i === q.value.correct) score.value += 1;
setTimeout(() => {
if (idx.value + 1 < QUESTIONS.length) {
idx.value += 1;
selected.value = null;
} else {
done.value = true;
}
}, 1200);
}
function restart() {
idx.value = 0;
score.value = 0;
selected.value = null;
done.value = false;
}
function btnStyle(i) {
if (selected.value === null) return "background:#21262d;border-color:#30363d;color:#e6edf3;";
if (i === q.value.correct)
return "background:rgba(35,134,54,0.3);border-color:rgba(126,231,135,0.5);color:#7ee787;";
if (i === selected.value)
return "background:rgba(248,81,73,0.2);border-color:rgba(248,81,73,0.5);color:#f85149;";
return "background:#21262d;border-color:#30363d;color:#484f58;";
}
</script>
<template>
<div v-if="done" class="min-h-screen bg-[#0d1117] flex items-center justify-center p-6">
<div class="bg-[#161b22] border border-[#30363d] rounded-2xl p-8 text-center w-full max-w-sm">
<p class="text-5xl mb-4">{{ pct === 100 ? "🏆" : pct >= 60 ? "🎯" : "📚" }}</p>
<h2 class="text-[#e6edf3] text-2xl font-bold mb-1">{{ score }}/{{ QUESTIONS.length }}</h2>
<p class="text-[#8b949e] text-sm mb-6">You scored {{ pct }}%</p>
<div class="h-2 bg-[#21262d] rounded-full mb-6">
<div class="h-full bg-[#58a6ff] rounded-full transition-all" :style="{ width: pct + '%' }"></div>
</div>
<button
@click="restart"
class="w-full py-2.5 bg-[#238636] border border-[#2ea043] text-white rounded-xl font-semibold text-sm hover:bg-[#2ea043] transition-colors"
>
Try Again
</button>
</div>
</div>
<div v-else class="min-h-screen bg-[#0d1117] flex items-center justify-center p-6">
<div class="bg-[#161b22] border border-[#30363d] rounded-2xl p-6 w-full max-w-sm">
<div class="flex items-center justify-between mb-4">
<span class="text-xs text-[#8b949e]">Question {{ idx + 1 }}/{{ QUESTIONS.length }}</span>
<span class="text-xs font-semibold text-[#58a6ff]">Score: {{ score }}</span>
</div>
<div class="h-1 bg-[#21262d] rounded-full mb-5">
<div class="h-full bg-[#58a6ff] rounded-full transition-all" :style="{ width: progressWidth + '%' }"></div>
</div>
<p class="text-[#e6edf3] font-semibold text-base mb-4 leading-snug">{{ q.question }}</p>
<div class="space-y-2">
<button
v-for="(a, i) in q.answers"
:key="i"
@click="pick(i)"
:disabled="selected !== null"
class="w-full text-left px-4 py-2.5 rounded-xl border text-sm transition-all duration-200"
:style="btnStyle(i)"
>
{{ a }}
</button>
</div>
</div>
</div>
</template><script>
let idx = 0;
let score = 0;
let selected = null;
let done = false;
const QUESTIONS = [
{
question: "Which CSS property creates a flex container?",
answers: ["display: grid", "display: block", "display: flex", "display: table"],
correct: 2,
},
{
question: "What does HTML stand for?",
answers: [
"Hyper Text Markup Language",
"Home Tool Markup Language",
"Hyperlinks and Text Markup Language",
"Hyper Text Modern Language",
],
correct: 0,
},
{
question: "Which company originally developed JavaScript?",
answers: ["Microsoft", "Netscape", "Google", "IBM"],
correct: 1,
},
{
question: "Which CSS unit is relative to the root element font size?",
answers: ["em", "px", "rem", "vh"],
correct: 2,
},
{
question: "The correct way to declare a variable in modern JavaScript?",
answers: ["var x = 5", "const x = 5", "int x = 5", "declare x = 5"],
correct: 1,
},
];
$: q = QUESTIONS[idx];
$: pct = Math.round((score / QUESTIONS.length) * 100);
$: progressWidth = (idx / QUESTIONS.length) * 100;
function pick(i) {
if (selected !== null) return;
selected = i;
if (i === q.correct) score += 1;
setTimeout(() => {
if (idx + 1 < QUESTIONS.length) {
idx += 1;
selected = null;
} else {
done = true;
}
}, 1200);
}
function restart() {
idx = 0;
score = 0;
selected = null;
done = false;
}
function btnStyle(i) {
if (selected === null) return "background:#21262d;border-color:#30363d;color:#e6edf3;";
if (i === q.correct)
return "background:rgba(35,134,54,0.3);border-color:rgba(126,231,135,0.5);color:#7ee787;";
if (i === selected)
return "background:rgba(248,81,73,0.2);border-color:rgba(248,81,73,0.5);color:#f85149;";
return "background:#21262d;border-color:#30363d;color:#484f58;";
}
</script>
{#if done}
<div class="min-h-screen bg-[#0d1117] flex items-center justify-center p-6">
<div class="bg-[#161b22] border border-[#30363d] rounded-2xl p-8 text-center w-full max-w-sm">
<p class="text-5xl mb-4">{pct === 100 ? "🏆" : pct >= 60 ? "🎯" : "📚"}</p>
<h2 class="text-[#e6edf3] text-2xl font-bold mb-1">{score}/{QUESTIONS.length}</h2>
<p class="text-[#8b949e] text-sm mb-6">You scored {pct}%</p>
<div class="h-2 bg-[#21262d] rounded-full mb-6">
<div class="h-full bg-[#58a6ff] rounded-full transition-all" style="width: {pct}%"></div>
</div>
<button
on:click={restart}
class="w-full py-2.5 bg-[#238636] border border-[#2ea043] text-white rounded-xl font-semibold text-sm hover:bg-[#2ea043] transition-colors"
>
Try Again
</button>
</div>
</div>
{:else}
<div class="min-h-screen bg-[#0d1117] flex items-center justify-center p-6">
<div class="bg-[#161b22] border border-[#30363d] rounded-2xl p-6 w-full max-w-sm">
<div class="flex items-center justify-between mb-4">
<span class="text-xs text-[#8b949e]">Question {idx + 1}/{QUESTIONS.length}</span>
<span class="text-xs font-semibold text-[#58a6ff]">Score: {score}</span>
</div>
<div class="h-1 bg-[#21262d] rounded-full mb-5">
<div class="h-full bg-[#58a6ff] rounded-full transition-all" style="width: {progressWidth}%"></div>
</div>
<p class="text-[#e6edf3] font-semibold text-base mb-4 leading-snug">{q.question}</p>
<div class="space-y-2">
{#each q.answers as a, i}
<button
on:click={() => pick(i)}
disabled={selected !== null}
class="w-full text-left px-4 py-2.5 rounded-xl border text-sm transition-all duration-200"
style={btnStyle(i)}
>
{a}
</button>
{/each}
</div>
</div>
</div>
{/if}Quiz Widget
Engagement-focused quiz widget. It allows for multiple-choice questions, provides immediate visual feedback on answers, and presents a final score breakdown at the end.
Features
- Dynamic question loading
- Progressive question flow with animations
- Immediate correct/incorrect styling
- Final score calculation
- Retake quiz functionality
- Mobile-optimized layout