Classic Quiz Play
Interactive quiz component with scoring, four templates, optional answer explanations, lead collection, sign-in gating, and a configurable additional CTA on the results screen. ##
Import
import { ClassicQuizPlay } from "fansunited-frontend-components";
import {
ClassicQuizPlayProps,
ClassicQuizTemplate,
} from "fansunited-frontend-core";Required props
| Prop | Type | Description |
|---|---|---|
entityId | string | Classic Quiz identifier (from the Fans United backend). |
sdk | FansUnitedSDKModel | SDK instance. |
template | ClassicQuizTemplate | Layout — STANDARD, SPLIT, OVERLAY, or DECK. |
language | LanguageType | Display language. |
ClassicQuizTemplate extends the shared WidgetTemplate with DECK, which is available
on this component only.
Optional props
| Prop | Type | Description |
|---|---|---|
themeOptions | CustomThemeOptions | See Theming. |
showAnswerExplanations | boolean | Show explanations on the results screen. |
hideCorrectOption | boolean | Never reveal which option was correct. See the section below. |
leads | LeadsOptions | See Lead Collection. |
consents | ConsentsConfig | Profile consent checkboxes. See Consents and the section below. |
imagePosition | "left" | "right" | STANDARD template only. |
deck | DeckOptions | DECK template only. See the section below. |
defaultImagePlaceholderUrl | string | Fallback image URL. |
userIsLoggedIn | boolean | Pass the host's auth state for sign-in-gated quizzes. |
signInCTA | SignInCTADetails | See Sign-in CTA. Supports gate, which defers the sign-in ask until the user has played — this is the only component that reads it. |
additionalCTA | AdditionalCTADetails | See Additional CTA. |
shareCTA | ShareCTADetails | See Share CTA. |
rulesDisplay | RulesDisplay | See Rules Display. |
callbacks | ClassicQuizPlayCallbacks | onFinish and onShare — see Callbacks. |
Consents
Collect profile consents alongside the participation. The shape is the shared ConsentDef — see Consents for the full field reference, the URL tokens and the checkbox-count rule.
import { ConsentsConfig } from "fansunited-frontend-core";
const consents: ConsentsConfig = {
items: [
{
consentId: "terms",
body: "I accept the {{privacyPolicyUrl}} and the {{termsAndConditionsUrl}}",
required: true,
position: "before",
},
{
consentId: "marketing",
body: "Send me news and offers by email",
defaultChecked: true,
position: "after",
},
],
labels: {
title: "Before we save your score",
ctaLabel: "Continue",
},
};
<ClassicQuizPlay {...otherProps} consents={consents} />Where each checkbox appears:
| Quiz configuration | Consent surface |
|---|---|
authRequirement: "LEAD" + leads.position set | The lead form, at the lead's position — all consents, each position ignored |
| No lead form | "before" → the start screen (forced to appear for every quiz type); "after" → its own step before the participation is submitted |
The "after" surface is a separate step rather than a checkbox next to the Finish button, so that reading it cannot inflate a timed quiz's score or let a countdown quiz auto-submit behind it. labels applies to both standalone surfaces; inside a lead form only requiredError is used.
Without ctaLabel, the start screen's button keeps the quiz's Start label and the consent step uses its Finish label — the step is what submits the participation. The last question's button then reads "Continue" rather than "Finish", since an "after" consent still has to be shown.
Deck template
DECK lays the quiz out as a casino table: every question is a face-down card, and the
player turns them over in whatever order they like. Picking an answer reveals the result
and the explanation immediately, on the card itself — there is no Next button. Once every
card has been played, the Finish button submits the participation.
import { DeckOptions } from "fansunited-frontend-core";
<ClassicQuizPlay
{...otherProps}
template={ClassicQuizTemplate.DECK}
deck={{ entrance: "deal", openIcon: "chevron" }}
/>deck key | Type | Default | Description |
|---|---|---|---|
entrance | "deal" | "flip" | "none" | "deal" | How the cards arrive. deal slides them in from the dealer's side, flip turns them over in place, none skips the animation. |
openIcon | "chevron" | "eye" | "flip" | "dot" | string | "chevron" | The mark shown on the open card, pointing at the question panel beneath it. Takes a preset name or an image URL — see below. |
Custom open-card mark
Pass a URL instead of a preset name to use your own artwork:
<ClassicQuizPlay
{...otherProps}
template={ClassicQuizTemplate.DECK}
deck={{ openIcon: "https://cdn.example.com/icons/reveal.svg" }}
/>| Recommendation | |
|---|---|
| Format | SVG. It stays sharp at any density and inherits no colour, so supply the fill you want. |
| Display size | Rendered in a 36 × 36 px box, matching the presets |
| Artwork ratio | Square. Non-square art is contained, not cropped, so it fits the box's shorter side and looks smaller. |
| Raster fallback | PNG at 72 × 72 (2x) or 108 × 108 (3x), transparent background |
| Orientation | Vertical or neutral. The mark sits on the card and the question panel opens beneath it, so a downward or non-directional glyph reads correctly. |
| Colour | Bake it into the asset. A custom mark is not tinted from the palette the way the presets are. |
| RTL | Not mirrored — the widget cannot tell whether your artwork is directional. Supply a direction-neutral mark, or two assets chosen by the language you pass. |
The mark is decorative (aria-hidden), so it needs no alt text, and it inherits the same
gentle nudge animation as the presets.
Both keys are optional, and so is deck itself — omit it for the defaults above. Every
colour, radius, font and spacing value still comes from themeOptions; the table felt and
the card backs are tinted from the palette's primary colour (or the quiz's branding colour
when it has one), and the card front shows the sponsor logo when one is configured, or a
? mark when it is not.
Two behaviours differ from the other templates:
optionsLayoutis ignored. The open card draws its own auto-fitting option grid.- Timing is per card. On a
countdownquiz the clock restarts each time a card is
turned over, and expiry banks a blank answer for that card. On atimedquiz the clock
only runs while a card is open, so browsing the table between cards is free and the
reported total is time spent answering rather than wall-clock time.
A signInCTA with gate: { position: "step", afterStep: n } fires after the player has
closed their n-th card, so the gate never interrupts a result they are still reading.
Answer explanations
Set showAnswerExplanations={true} to reveal per-question explanations on the results screen. The explanations come from the quiz's question configuration on the backend.
<ClassicQuizPlay {...otherProps} showAnswerExplanations={true} />Hiding the correct option
By default, answering a question lights up the correct option in green even when the
player picked something else, and the results screen prints the correct answer for every
question they got wrong. Set hideCorrectOption={true} to suppress both, so a player
can never learn the answers and pass them on.
<ClassicQuizPlay {...otherProps} hideCorrectOption={true} />The player still sees how they did — their own pick is coloured green or red after each
answer, the progress indicator still marks each question with a tick or a cross, and the
results screen still shows their score and which questions they got wrong. The only thing
withheld is which option was the right one.
Because a question's explanation usually states the answer, this also suppresses the
explanation text on the results screen. hideCorrectOption wins when both it and
showAnswerExplanations are set: the per-question breakdown still appears, with the
question and the player's own answer, but without the correct answer or the explanation.
This is a presentation-level setting. The quiz's correct options are still part of the
API response the widget loads, so anyone inspecting network traffic in their browser can
read them. Treat it as a deterrent against casual answer-sharing, not as a guarantee.
Examples
Basic
<ClassicQuizPlay
entityId="quiz-123"
sdk={sdk}
template={ClassicQuizTemplate.STANDARD}
language="en"
/>Fully customized
<ClassicQuizPlay
entityId="quiz-123"
sdk={sdk}
template={ClassicQuizTemplate.OVERLAY}
language="en"
showAnswerExplanations
userIsLoggedIn={false}
signInCTA={{
defaultLabel: "Sign in",
onClick: openSignIn,
// Play the first three questions, then ask. Omit `gate` to keep the
// sign-in screen in front of the whole quiz.
gate: { position: "step", afterStep: 3 },
}}
leads={{
position: "after",
fields: ["fullName", "email"],
campaignId: "quiz-2024",
campaignName: "Quiz Campaign 2024",
phoneCountryCode: "44",
syncWithProfile: true,
}}
additionalCTA={{
defaultLabel: "Learn more",
url: "https://example.com",
target: "_blank",
}}
rulesDisplay={{ type: "modal" }}
themeOptions={{ mode: "dark" }}
/>Updated about 7 hours ago
