Quiz Development with Result Calculation on Website
A quiz is a marketing tool that generates leads through engagement. A properly constructed quiz collects contacts, segments the audience, and recommends a product. A poorly designed one is just a set of questions without value to the user.
Quiz Types
Typological — each answer adds points to one or more "profiles". The profile with the highest score wins at the end. Used for "What type of customer are you", "Let's find the right plan for you".
Scoring — answers give numerical points, the final result is determined by a range. Typical for knowledge tests, level assessment.
Branching — the next question depends on the previous answer. Allows building complex scenarios with different outcomes.
Recommendation — the quiz collects parameters and recommends a specific product, plan, or specialist on output.
Data Structure
interface QuizQuestion {
id: string;
text: string;
type: 'single' | 'multiple' | 'scale';
answers: QuizAnswer[];
nextQuestion?: string | ((answers: Record<string, string[]>) => string); // for branching
}
interface QuizAnswer {
id: string;
text: string;
scores: Record<string, number>; // { profile_a: 3, profile_b: 1 }
image?: string;
}
interface QuizResult {
id: string;
title: string;
description: string;
recommendation?: string;
cta?: { text: string; url: string };
minScore?: number; // for scoring type
maxScore?: number;
}
Calculation Engine
class QuizEngine {
constructor(questions, results) {
this.questions = questions;
this.results = results;
this.answers = {}; // questionId -> answerId[]
this.scores = {};
}
answer(questionId, answerIds) {
this.answers[questionId] = answerIds;
const question = this.questions.find(q => q.id === questionId);
for (const answerId of answerIds) {
const answer = question.answers.find(a => a.id === answerId);
if (!answer?.scores) continue;
for (const [profile, score] of Object.entries(answer.scores)) {
this.scores[profile] = (this.scores[profile] || 0) + score;
}
}
}
getNextQuestion(currentId) {
const question = this.questions.find(q => q.id === currentId);
if (typeof question.nextQuestion === 'function') {
return question.nextQuestion(this.answers);
}
return question.nextQuestion;
}
calculateResult() {
// Typological: winner by score
const [topProfile] = Object.entries(this.scores)
.sort(([, a], [, b]) => b - a);
return this.results.find(r => r.id === topProfile?.[0]) ?? this.results[0];
}
getTotalScore() {
return Object.values(this.scores).reduce((s, v) => s + v, 0);
}
}
React Quiz Component
function Quiz({ config }) {
const engine = useRef(new QuizEngine(config.questions, config.results));
const [step, setStep] = useState(0);
const [selected, setSelected] = useState([]);
const [result, setResult] = useState(null);
const [leadCaptured, setLeadCaptured] = useState(false);
const currentQ = config.questions[step];
const progress = ((step / config.questions.length) * 100).toFixed(0);
function handleNext() {
engine.current.answer(currentQ.id, selected);
setSelected([]);
if (step + 1 >= config.questions.length) {
setResult(engine.current.calculateResult());
} else {
setStep(s => s + 1);
}
}
if (result && !leadCaptured) {
return <LeadCapture onSubmit={(contact) => {
submitLead({ contact, result, answers: engine.current.answers });
setLeadCaptured(true);
}} />;
}
if (result && leadCaptured) {
return <QuizResult result={result} score={engine.current.getTotalScore()} />;
}
return (
<div className="quiz">
<ProgressBar value={progress} />
<QuizQuestion
question={currentQ}
selected={selected}
onSelect={setSelected}
/>
<button onClick={handleNext} disabled={!selected.length}>
{step + 1 < config.questions.length ? 'Next' : 'Get Result'}
</button>
</div>
);
}
Lead Capture Before Result
Classic mechanics: the user answers all questions but only sees the result after entering their email (or phone). Conversion is higher than a regular subscription form because the person has already invested time.
function LeadCapture({ onSubmit }) {
const { register, handleSubmit } = useForm();
return (
<form onSubmit={handleSubmit(onSubmit)}>
<h3>Your result is ready!</h3>
<p>Enter your email to get personalized recommendations</p>
<input {...register('email', { required: true })} type="email" placeholder="[email protected]" />
<input {...register('name')} placeholder="Name (optional)" />
<button type="submit">Show Result</button>
</form>
);
}
Analytics
For quizzes, it's important to track not only final conversions but also dropout at each step:
// GTM / GA4
function trackStep(questionIndex, questionId) {
window.dataLayer?.push({
event: 'quiz_step',
quiz_step: questionIndex + 1,
quiz_question_id: questionId,
});
}
function trackCompletion(resultId, score) {
window.dataLayer?.push({
event: 'quiz_complete',
quiz_result: resultId,
quiz_score: score,
});
}
Transition Animations
Transitions between questions improve the user experience. Simple animation via CSS:
.quiz-question {
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from { opacity: 0; transform: translateX(24px); }
to { opacity: 1; transform: translateX(0); }
}
For React — framer-motion with AnimatePresence for smooth removal of the previous question.
Timeframe
A simple scoring quiz with 5–10 questions, lead capture, and result display — 3–4 working days. A branching quiz with multiple profiles, animation, CRM integration, and A/B testing of configurations — 8–12 days.







