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

PropTypeDescription
entityIdstringClassic Quiz identifier (from the Fans United backend).
sdkFansUnitedSDKModelSDK instance.
templateClassicQuizTemplateLayout — STANDARD, SPLIT, OVERLAY, or DECK.
languageLanguageTypeDisplay language.

ClassicQuizTemplate extends the shared WidgetTemplate with DECK, which is available
on this component only.

Optional props

PropTypeDescription
themeOptionsCustomThemeOptionsSee Theming.
showAnswerExplanationsbooleanShow explanations on the results screen.
hideCorrectOptionbooleanNever reveal which option was correct. See the section below.
leadsLeadsOptionsSee Lead Collection.
consentsConsentsConfigProfile consent checkboxes. See Consents and the section below.
imagePosition"left" | "right"STANDARD template only.
deckDeckOptionsDECK template only. See the section below.
defaultImagePlaceholderUrlstringFallback image URL.
userIsLoggedInbooleanPass the host's auth state for sign-in-gated quizzes.
signInCTASignInCTADetailsSee Sign-in CTA. Supports gate, which defers the sign-in ask until the user has played — this is the only component that reads it.
additionalCTAAdditionalCTADetailsSee Additional CTA.
shareCTAShareCTADetailsSee Share CTA.
rulesDisplayRulesDisplaySee Rules Display.
callbacksClassicQuizPlayCallbacksonFinish 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 configurationConsent surface
authRequirement: "LEAD" + leads.position setThe 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 keyTypeDefaultDescription
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
FormatSVG. It stays sharp at any density and inherits no colour, so supply the fill you want.
Display sizeRendered in a 36 × 36 px box, matching the presets
Artwork ratioSquare. Non-square art is contained, not cropped, so it fits the box's shorter side and looks smaller.
Raster fallbackPNG at 72 × 72 (2x) or 108 × 108 (3x), transparent background
OrientationVertical or neutral. The mark sits on the card and the question panel opens beneath it, so a downward or non-directional glyph reads correctly.
ColourBake it into the asset. A custom mark is not tinted from the palette the way the presets are.
RTLNot 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:

  • optionsLayout is ignored. The open card draws its own auto-fitting option grid.
  • Timing is per card. On a countdown quiz the clock restarts each time a card is
    turned over, and expiry banks a blank answer for that card. On a timed quiz 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" }}
/>

Did this page help you?