Features
Cross-cutting capabilities shared across widgets. Each section sets a global default in FuWidget.init() that individual widgets can override via data attributes.
Lead Collection
Lead collection follows a hierarchy of configurations, allowing both global and content-specific settings.
Important:Lead collection must be enabled in the Management Portal (Auth Requirement =
"LEAD") for any lead collection configuration to take effect.
Configuration Hierarchy
- Management Portal Configuration
- Set under Configuration → Auth Requirement
- Enables/disables lead collection for specific content
- Options:
"FREE","LEAD","REGISTERED"
- Global Widget Configuration
- Set during widget initialization
- Applies to all content unless overridden
- HTML Attribute Configuration
- Set per widget instance
- Overrides global configuration
Example Configuration
- Enable leads in Management Portal:
Configuration -> Auth Requirement -> LEAD
- Global configuration in widget initialization:
FuWidget.init({
// ... other config
leads: {
defaultFields: ["fullName", "email"], // Default fields for all widgets
position: "after", // Default position
campaignId: "default-campaign", // Default campaign ID
campaignName: "Default Campaign", // Default campaign name
syncWithProfile: false, // Default profile synchronization setting
},
});- Individual widget configuration:
<div
data-component="fu-widget"
data-content-type="classic-quiz"
data-content-id="quiz-123"
data-lead-fields="firstName,lastName,email" // Overrides defaultFields
data-lead-position="before" // Overrides position
data-campaign-id="summer-campaign" // Overrides campaignId
data-campaign-name="Summer Campaign 2024" // Overrides campaignName
data-lead-sync-with-profile="true" // Overrides syncWithProfile (default: false)
></div>Available Lead Fields
fullName— Full name inputfirstName— First name inputlastName— Last name inputemail— Email inputgender— Gender selectioncountry— Country selectionphoneCountryCode— Phone country codephoneNumber— Phone number input
Lead Form Position
before— Lead form appears before contentafter— Lead form appears after content
Profile Synchronization
The data-lead-sync-with-profile attribute controls whether anonymous user profiles are synchronized with the database after successful lead form submission. This feature is essential for displaying user names in leaderboards, tracking user participation across widgets, and enabling data export capabilities.
Supported Widgets:
- Classic Quiz
- Personality Quiz
- Match Quiz
- Poll
- Collect Lead
Configuration Options
"true"— Enable profile synchronization"false"— Disable profile synchronization (default)
Global Configuration
FuWidget.init({
// ... other config
leads: {
// ... other lead config
syncWithProfile: true, // Enable profile sync by default for all widgets
},
});Per-Widget Configuration
<div
data-component="fu-widget"
data-content-type="classic-quiz"
data-content-id="quiz-123"
data-lead-sync-with-profile="true"
></div>Use Cases
Enable when:
- You want to display user nicknames or names in leaderboards across multiple widgets
- You plan to export user data for marketing campaigns
- You need to analyze user behavior across different content types
Disable when:
- You only need basic lead collection without user tracking
- Privacy regulations require minimal data retention
- You want to reduce database storage requirements
- You're collecting leads for one-time campaigns only
Technical Details
When profile synchronization is enabled:
- Anonymous user profiles are linked to the submitted lead information
- User participation data is preserved across widget interactions
- Export functionality can access complete user participation history
When profile synchronization is disabled:
- Lead information is collected but not linked to user profiles
- Each widget interaction is treated independently
- Export functionality is limited to basic lead information
See Widgets → Collect Lead for a standalone lead form with custom fields and consents.
Consents
Consents collect an explicit agreement alongside a participation and record it against the user's Fans United profile, with the game as the entity. They are supported by the six game widgets — Classic Quiz, Personality Quiz, Poll, Match Quiz, Either/Or and Event Game.
<div
data-component="fu-widget"
data-content-type="classic-quiz"
data-content-id="your-quiz-id"
data-consents='[{"consentId":"terms","body":"I accept the {{privacyPolicyUrl}} and the {{termsAndConditionsUrl}}","required":true},{"consentId":"marketing","body":"Send me news and offers by email","defaultChecked":true,"position":"after"}]'
data-consents-labels='{"title":"Before we save your score","ctaLabel":"Continue"}'
></div>Configuration Attributes
| Attribute | Description | Options/Format | Default |
|---|---|---|---|
data-consents | JSON array of consent definitions | Array of consent objects | Global config |
data-consents-labels | JSON object of copy for the consent surfaces | Object (see below) | Built-in i18n |
Consent Object Fields
| Field | Type | Description |
|---|---|---|
consentId | string | Unique identifier for the consent (e.g. "terms", "marketing") |
body | string | Label content. Supports HTML and the URL tokens below |
required | boolean | When true, blocks the widget's CTA until ticked. Defaults to false |
defaultChecked | boolean | Pre-check the box on load. Defaults to false |
position | "before" | "after" | Which surface the checkbox appears on. Honoured by all six game widgets. Defaults to "before" |
Labels
All four fields are optional and fall back to the built-in translations for the widget's language.
| Field | Description |
|---|---|
title | Heading above the consent list |
description | Supporting copy under the heading |
ctaLabel | Button label on the consent surface |
requiredError | Message shown when a required consent has not been ticked |
Body Tokens
{{privacyPolicyUrl}} and {{termsAndConditionsUrl}} are replaced with links to the entity's own branding.urls, configured in the back office. A token with no URL behind it renders as plain text and logs a warning. Raw HTML in body works too, so a literal <a href="..."> is also fine.
Where Each Checkbox Appears
| Widget configuration | Consent surface |
|---|---|
authRequirement: "LEAD" with a lead form position | Inside the lead form, at the lead's position — all consents, each position ignored |
| No lead form | "before" → the start screen; "after" → its own step between the last answer and the submit |
Behavior
- A required consent blocks the surface's CTA until it is ticked.
- Inside a lead form, configured consents replace the built-in privacy-policy checkbox, and the form's submit is blocked until the required ones are ticked.
- Consents the user already accepted are filtered out on mount, so a returning player is not asked twice.
- With
"before"consents and no lead form, the start screen is shown for every quiz type — including ones that would otherwise drop the user straight into question 1. "after"is a dedicated step rather than a checkbox beside the Finish button, so reading it cannot inflate a timed quiz's score or let a countdown quiz auto-submit behind it.- A failed consent submission is logged and swallowed — the participation is still submitted.
Global Configuration
FuWidget.init({
// ... other config
consents: {
items: [
{
consentId: "terms",
body: "I accept the {{privacyPolicyUrl}} and the {{termsAndConditionsUrl}}",
required: true,
position: "before",
},
],
labels: {
title: "Before we save your score",
requiredError: "Please accept the required consents to continue.",
},
},
});A data-consents attribute on an element replaces the whole global consents.items array for that widget; data-consents-labels replaces consents.labels.
Predictor and Top X have their own consent gate with a different surface (a modal before the first prediction) and keep their own
data-predictor-consents/data-top-x-consentsattributes — see Widgets → Predictor and Widgets → Top X. Collect Lead also reuses thedata-consentsname for its own form checkboxes; see Widgets → Collect Lead.
Sign-in & Additional CTA
The sign-in call-to-action (CTA) feature lets you customise the label and click behavior for authentication prompts in widgets that require user sign-in. This is particularly useful for Classic Quiz, Poll, and Either/Or widgets where users need to authenticate to participate.
The system automatically detects when a user is authenticated by fetching their profile using sdk.profile.getOwn().getInfo() and checking the anonymous property. Anonymous users are considered not authenticated and will see the sign-in CTA when required.
Configuration Hierarchy
The sign-in CTA configuration follows this priority order (highest to lowest):
- Data attributes — Widget-specific overrides
- Main config — Global defaults
- Translated defaults — Localized fallback values based on widget type
- Built-in defaults — Final fallback values
Global Configuration
FuWidget.init({
// ... other config
signInCTA: {
defaultLabel: "Sign in to participate",
onClick: () => {
// Your custom sign-in logic
window.location.href = "/signin";
// Or show a modal
// showSignInModal();
// Or trigger your authentication system
// authService.showLogin();
},
},
});Per-Widget Configuration
<div
data-component="fu-widget"
data-content-type="classic-quiz"
data-content-id="your-quiz-id"
data-signin-cta-label="Join Now to Vote!"
data-signin-cta-onclick="showCustomSignInModal()"
></div>Configuration Options
signInCTA Object Properties
| Property | Type | Required | Description |
|---|---|---|---|
defaultLabel | string | No | The text displayed on the sign-in button. When omitted, each component uses its own translated copy for the widget language |
onClick | function | No | Function to execute when the sign-in button is clicked |
url | string | No | Direct URL for the CTA link. Can be used instead of or in addition to onClick |
target | string | No | Link target for the URL. Options: _self, _blank. Default: _self |
additionalCTA Object Properties
The additionalCTA follows the same structure as signInCTA and can be used to add a secondary call-to-action button in widgets.
| Property | Type | Required | Description |
|---|---|---|---|
defaultLabel | string | No | The text displayed on the additional CTA button |
onClick | function | No | Function to execute when the additional CTA button is clicked |
url | string | No | Direct URL for the CTA link. Can be used instead of or in addition to onClick |
target | string | No | Link target for the URL. Options: _self, _blank. Default: _self |
Data Attributes
Sign-in CTA:
| Attribute | Description | Example |
|---|---|---|
data-signin-cta-label | Override the button label for this widget | "Join Now!" |
data-signin-cta-onclick | JavaScript code to execute on click | "window.open('/signin', '_blank')" |
data-signin-cta-url | Direct URL for the CTA link | "https://yourdomain.com/signin" |
data-signin-cta-target | Link target (self, blank, etc.) | "_blank" |
Additional CTA:
| Attribute | Description | Example |
|---|---|---|
data-additional-cta-label | Override the button label for this widget | "Learn More" |
data-additional-cta-onclick | JavaScript code to execute on click | "console.log('clicked')" |
data-additional-cta-url | Direct URL for the CTA link | "https://yourdomain.com/info" |
data-additional-cta-target | Link target (self, blank) | "_blank" |
Sign-in Gate
By default a widget that requires a registered user shows a sign-in wall in front of the game — nothing is playable until the visitor signs in. The sign-in gate moves that wall later, so the visitor plays first and only signs in when their result is about to be saved. Their answers are held in sessionStorage and restored after they come back, so nothing is lost.
Supported by Classic Quiz, Personality Quiz and Match Quiz. Other widgets keep the wall in front of the game.
<div
data-component="fu-widget"
data-content-type="classic-quiz"
data-content-id="your-quiz-id"
data-signin-cta-url="https://yourdomain.com/signin"
data-signin-gate-position="step"
data-signin-gate-after-step="3"
data-signin-gate-title="Enjoying the quiz?"
data-signin-gate-description="Sign in to save your score and see how you rank."
></div>Positions
| Value | When the gate fires |
|---|---|
"before" | Default — the wall in front of the game, i.e. the historical behaviour |
"step" | After data-signin-gate-after-step questions. The visitor plays that many, then signs in |
"after" | At the end — the visitor plays the whole thing, then signs in before the result is saved |
Configuration Attributes
| Attribute | Description | Options/Format | Default |
|---|---|---|---|
data-signin-gate-position | When the gate fires | "before", "step", "after" | "before" |
data-signin-gate-after-step | Questions playable before the gate opens. Required by "step" | Integer ≥ 1 (1-based) | None |
data-signin-gate-title | Heading on the gate. Ignored by "before" | Any string | Built-in i18n |
data-signin-gate-description | Supporting copy on the gate. Ignored by "before" | Any string | Built-in i18n |
Behavior
- Deferring needs a sign-in CTA the user can actually act on —
data-signin-cta-url,data-signin-cta-onclick, or a custom component. Without one the gate would strand the visitor behind a dead button mid-game, so it falls back to"before"and logs a warning. "step"without a valid numericafter-step(or below step 1) falls back to"before"with a warning.- An
after-stepat or beyond the last question is treated as"after"— there is nothing left to withhold. data-signin-gate-title/-descriptiondo not apply to"before", which keeps the standalone sign-in screen's own copy.- The gate only ever appears for entities whose Auth Requirement is
REGISTERED; aFREEentity never gates, whatever the position.
Global Configuration
FuWidget.init({
// ... other config
signInCTA: {
defaultLabel: "Sign in to save your score",
url: "https://yourdomain.com/signin",
gate: {
position: "after", // "before" (default), "step" or "after"
afterStep: 3, // Only used by "step" — 1-based
labels: {
title: "Enjoying the quiz?",
description: "Sign in to save your score and see how you rank.",
},
},
},
});Per-element data-signin-gate-* attributes override the global gate for that one widget.
Authentication Detection
The widget system automatically determines user authentication status by:
- Fetching User Profile: Uses
sdk.profile.getOwn().getInfo()when the SDK is available - Checking Anonymous Status: Examines the
anonymousproperty in the profile response - Setting Authentication State: Users with
anonymous: falseare considered authenticated - Reactive Updates: Authentication status is checked once and made available to all widgets
Use Cases
Redirect to Sign-in Page
signInCTA: {
defaultLabel: "Sign In",
url: '/login',
target: '_self'
}Or using onClick:
signInCTA: {
defaultLabel: "Sign In",
onClick: () => {
window.location.href = '/login';
}
}Show Modal Dialog
signInCTA: {
defaultLabel: "Login to Continue",
onClick: () => {
document.getElementById('signin-modal').style.display = 'block';
}
}Open Sign-in in New Tab
signInCTA: {
defaultLabel: "Sign In",
url: '/signin',
target: '_blank'
}Or using onClick:
signInCTA: {
defaultLabel: "Sign In",
onClick: () => {
window.open('/signin', '_blank');
}
}Trigger Third-party Authentication
signInCTA: {
defaultLabel: "Login with SSO",
onClick: () => {
// Trigger your authentication provider
authProvider.login();
}
}Using Additional CTA
You can add a secondary call-to-action button alongside the sign-in CTA:
FuWidget.init({
// ... other config
signInCTA: {
defaultLabel: "Sign In",
url: '/signin',
target: '_self'
},
additionalCTA: {
defaultLabel: "Learn More",
url: '/about',
target: '_blank'
}
});Or configure it per widget using data attributes:
<div
data-component="fu-widget"
data-content-type="classic-quiz"
data-content-id="your-quiz-id"
data-additional-cta-label="View Rules"
data-additional-cta-url="/rules"
data-additional-cta-target="_blank"
></div>NOTE: Additional CTA is only supported in Classic Quiz, Poll, Personality Quiz and Match Quiz widgets.
Security Considerations
- The
data-signin-cta-onclickattribute executes JavaScript code, so ensure you trust the source. - Validate and sanitize any user-provided onclick code in production environments.
- Consider using CSP (Content Security Policy) headers to restrict inline script execution.
Share CTA
The Classic Quiz renders a Share Result button on its results screen. By default the button uses the browser's native share (navigator.share), falling back to an Android bridge and then clipboard copy. The shareCTA feature lets you replace this button with your own label, click handler, or URL — the same shape and priority as signInCTA and additionalCTA.
NOTE: Share CTA is only supported in the Classic Quiz widget.
Priority Order
- Click handler (
onClick/data-share-cta-onclick) — a button that calls your handler. - URL navigation (
url/data-share-cta-url) — a button that navigates to the URL. - Default native share — used when
shareCTAis omitted entirely.
Providing a handler or URL disables the default native share. If you want to keep native share and be notified when it runs, omit
shareCTAand use theonSharecallback instead.
Global Configuration
FuWidget.init({
// ... other config
shareCTA: {
defaultLabel: "Share Result",
onClick: () => openMyShareModal(),
},
});Per-Widget Configuration
<div
data-component="fu-widget"
data-content-type="classic-quiz"
data-content-id="your-quiz-id"
data-share-cta-label="Share"
data-share-cta-url="https://example.com/share?quiz=123"
data-share-cta-target="_blank"
></div>shareCTA Object Properties
| Property | Type | Required | Description |
|---|---|---|---|
defaultLabel | string | No | The text displayed on the share button |
onClick | function | No | Function to execute when the share button is clicked |
url | string | No | Direct URL for the share link |
target | string | No | Link target for the URL. Options: _self, _blank. Default: _self |
Data Attributes
| Attribute | Description | Example |
|---|---|---|
data-share-cta-label | Override the button label for this widget | "Share" |
data-share-cta-onclick | JavaScript code to execute on click | "navigator.share({ url: location.href })" |
data-share-cta-url | Direct URL for the share link | "https://example.com/share" |
data-share-cta-target | Link target (self, blank) | "_blank" |
Callbacks
Play widgets emit observer callbacks so the host page can react when a user finishes (or shares) a participation — useful for analytics, audience tracking, or kicking off follow-up flows. Callbacks are fire-and-forget: they observe what happens, they do not change widget behavior. To override the share button UX, use shareCTA instead.
Available Callbacks
| Callback | Fires when | Supported widgets |
|---|---|---|
onFinish | A participation is successfully submitted in this session | Classic Quiz · Poll · Personality Quiz · Match Quiz · Event Game · Either/Or |
onShare | The default (native) share button is clicked | Classic Quiz |
onEvent | Any step of the user journey — 24 named events | Predictor |
onFinish and onShare each receive the SDK participation model as their single argument (e.g. the quiz summary, poll vote, or prediction). onFinish fires only on a fresh participation — it does not fire when a widget loads onto an already-finished state.
onEvent is a different shape: a typed analytics event ({ widget, name, params, context }) rather than a participation model, fired throughout the journey rather than once at the end. See API Reference → Predictor Analytics API for the event catalogue, and for FuWidget.predictor.subscribe, which delivers the same stream to a script that loads separately from your page source — the usual choice for a tag manager.
Configuration
Callbacks are registered only through FuWidget.init(), as real JavaScript functions:
FuWidget.init({
// ... other config
callbacks: {
onFinish: (payload) => {
window.dataLayer.push({ event: "fu_widget_finish", payload });
},
onShare: (payload) => {
window.dataLayer.push({ event: "fu_widget_share", payload });
},
},
});No data attribute. Callbacks are intentionally not exposed as per-widget data attributes. A data attribute would have to be evaluated as code (
new Function), which becomes a script-injection risk if the markup is ever templated from untrusted input. Register callbacks throughFuWidget.init({ callbacks })instead — the sameonFinish/onShare/onEventfire regardless of how the widget itself is embedded. The one related attribute,data-predictor-analytics, is a boolean that turns on a fixeddataLayerpush — it carries no code.
Rules Display
The Rules Display feature controls how game rules are presented to users. You can choose between a modal dialog (default) or a link to an external page.
Supported Widgets
- Classic Quiz Widget
- Poll Widget
- Personality Quiz Widget
- Match Quiz Widget
- Event Game Widget
Display Types
Modal Display (Default)
When type is set to "modal" or not specified, rules are displayed in a modal dialog. The modal shows the rules content from the backend (if available).
FuWidget.init({
// ... other config
rulesDisplay: {
type: "modal" // This is the default behavior
}
});Link Display
When type is set to "link", a link is displayed instead of a modal. Useful when you want to direct users to a dedicated rules page.
FuWidget.init({
// ... other config
rulesDisplay: {
type: "link",
url: "/game-rules",
target: "_blank" // Opens in new tab
}
});Global Configuration
FuWidget.init({
// ... other config
rulesDisplay: {
type: "link", // "modal" or "link"
url: "https://example.com/rules", // Required when type is "link"
target: "_blank" // Optional: "_blank", "_self", "_parent", "_top"
}
});Per-Widget Configuration
Modal Display (Per Widget)
<div
data-component="fu-widget"
data-content-type="classic-quiz"
data-content-id="your-quiz-id"
data-rules-display-type="modal"
></div>Link Display (Per Widget)
<div
data-component="fu-widget"
data-content-type="poll"
data-content-id="your-poll-id"
data-rules-display-type="link"
data-rules-display-url="https://example.com/poll-rules"
data-rules-display-target="_blank"
></div>Configuration Options
rulesDisplay Object Properties
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
type | "modal" | "link" | No | "modal" | Display type: modal dialog or external link |
url | string | Yes* | - | URL to open when type is "link" (*Required only for link type) |
target | "_blank" | "_self" | "_parent" | "_top" | No | - | Link target attribute (only used when type is "link") |
Data Attributes
| Attribute | Type | Description |
|---|---|---|
data-rules-display-type | "modal" | "link" | Display type for this specific widget |
data-rules-display-url | string | URL to open when type is "link" |
data-rules-display-target | "_blank" | "_self" | "_parent" | "_top" | Link target attribute |
Configuration Priority
The configuration follows this priority order (highest to lowest):
- Data attributes — Widget-specific overrides
- Main config — Global defaults
- Component defaults — Built-in fallback (modal display)
Usage Examples
Example 1: Global Modal Display
FuWidget.init({
// ... other config
rulesDisplay: {
type: "modal"
}
});All widgets will display rules in a modal dialog.
Example 2: Global Link Display
FuWidget.init({
// ... other config
rulesDisplay: {
type: "link",
url: "https://example.com/general-rules",
target: "_blank"
}
});All widgets will display a link to the rules page that opens in a new tab.
Example 3: Mixed Configuration
FuWidget.init({
// ... other config
rulesDisplay: {
type: "link",
url: "https://example.com/general-rules",
target: "_blank"
}
});<!-- This quiz uses the global link configuration -->
<div
data-component="fu-widget"
data-content-type="classic-quiz"
data-content-id="quiz-1"
></div>
<!-- This poll overrides to use modal display -->
<div
data-component="fu-widget"
data-content-type="poll"
data-content-id="poll-1"
data-rules-display-type="modal"
></div>
<!-- This quiz overrides with a different rules URL -->
<div
data-component="fu-widget"
data-content-type="classic-quiz"
data-content-id="quiz-2"
data-rules-display-type="link"
data-rules-display-url="https://example.com/special-quiz-rules"
data-rules-display-target="_self"
></div>Best Practices
- Use Modal for Short Rules: If your rules are brief and can be displayed inline, use the modal display type for better user experience.
- Use Link for Detailed Rules: If you have comprehensive rules or legal terms, use the link display type to direct users to a dedicated page.
- Consistent Target Behavior: When using link type, be consistent with the
targetattribute across your widgets. Use"_blank"to open in a new tab (recommended for external links) or"_self"to open in the same window. - Per-Widget Customization: Use data attributes to customize rules display for specific widgets that need different behavior from the global configuration.
Authentication Requirements
Widgets support different authentication requirements that can be configured in the Management Portal.
Authentication Requirement Types
| Requirement | Description | Sign-in CTA Behavior |
|---|---|---|
null (None) | No authentication required | Never shown |
"FREE" | Just for fun, without auth | Never shown |
"LEAD" | Lead collection required | Not shown (handled by lead collection) |
"REGISTERED" | User registration required | Shown to anonymous users |
Supported Widgets
The following widgets support authentication requirements and will show the sign-in CTA when needed:
- Classic Quiz Widget: Shows sign-in CTA instead of "Start Quiz" button (uses backend
authRequirement) - Personality Quiz Widget: Shows sign-in CTA instead of "Start Quiz" button (uses backend
authRequirement) - Either-Or Widget: Shows sign-in CTA instead of "Start" button (uses backend
authRequirement) - Poll Widget: Shows sign-in CTA instead of voting options (uses backend
authRequirement) - Player of the Match Widget: Shows sign-in CTA instead of vote buttons (uses configurable
authRequirement)
Configuration
Authentication requirements are configured in the Management Portal for each piece of content. When set to "REGISTERED", the widget will:
- Check if the user is authenticated (not anonymous)
- Show the appropriate interface based on authentication status
- Allow authenticated users to participate immediately
- Prompt unauthenticated users to sign in first
See Getting Started → Authentication for how the widget connects to the API.
Theming
The comprehensive theming system, used by every widget:
import { CustomThemeOptions } from "fansunited-frontend-core";
// Default theme options
const themeOptions: CustomThemeOptions = {
mode: "light", // or 'dark'
colorSchemes: {
light: {
palette: {
success: {
plainColor: "#4CAF50",
outlinedBorder: "#4CAF50",
},
danger: {
softBg: "#FEE4E2",
plainColor: "#F44336",
outlinedBorder: "#F44336",
},
primary: {
plainColor: "#1A77D2",
outlinedBorder: "#1A77D2",
onPrimary: "#FAFAFA",
primaryContainer: "#2397F3",
},
warning: {
softBg: "#FEF0C7",
plainColor: "#DC6803",
},
},
textPrimary: "#212121",
textSecondary: "#212121",
textColor: "#212121",
textDisabled: "#212121",
surface: "#FFFFFF",
onSurface: "#F5F5F5",
surfaceVariant: "#EEEEEE",
surfaceTintDim: "#212121",
surfaceInverse: "#F5F5F5",
outlineEnabledBorder: "#E0E0E0",
secondaryContainer: "#BDBDBD",
},
dark: {
palette: {
primary: {
plainColor: "#1A77D2",
outlinedBorder: "#1A77D2",
onPrimary: "#FAFAFA",
primaryContainer: "#2397F3",
},
success: {
plainColor: "#4CAF50",
outlinedBorder: "#4CAF50",
},
danger: {
softBg: "#430A0A",
plainColor: "#F44336",
outlinedBorder: "#F44336",
},
warning: {
softBg: "#FEF0C7",
plainColor: "#DC6803",
},
},
textPrimary: "#FAFAFA",
textSecondary: "#FAFAFA",
textColor: "#FAFAFA",
textDisabled: "#FAFAFA",
surface: "#424242",
onSurface: "#212121",
surfaceVariant: "#616161",
surfaceTintDim: "#FAFAFA",
surfaceInverse: "#FAFAFA",
outlineEnabledBorder: "#757575",
secondaryContainer: "#757575",
},
},
customBreakpoints: {
values: {
xs: 0,
sm: 444,
md: 600,
lg: 900,
xl: 1200,
xxl: 1536,
},
},
spacingScale: {
"3xs": "2px",
"2xs": "4px",
xs: "8px",
sm: "12px",
md: "16px",
lg: "24px",
xl: "32px",
"2xl": "40px",
"3xl": "48px",
},
customFontFamily: {
light: {
primary: "Ubuntu, sans-serif",
secondary: "Roboto, sans-serif",
},
dark: {
primary: "Ubuntu, sans-serif",
secondary: "Roboto, sans-serif",
},
},
customRadius: {
light: {
none: "0px",
"2xs": "2px",
xs: "4px",
sm: "8px",
md: "12px",
lg: "16px",
xl: "24px",
"2xl": "232px",
full: "1000px",
},
dark: {
none: "0px",
"2xs": "2px",
xs: "4px",
sm: "8px",
md: "12px",
lg: "16px",
xl: "24px",
"2xl": "232px",
full: "1000px",
},
},
border: {
light: {
size: "1px",
},
dark: {
size: "2px",
},
},
imageBackgroundGradient: {
light: {
standard:
"linear-gradient(270deg, rgba(255, 255, 255, 0) 0%, rgba(18, 18, 18, 0.8) 100%)",
split:
"linear-gradient(270deg, rgba(255, 255, 255, 0) 0%, rgba(18, 18, 18, 0.8) 100%)",
},
dark: {
standard:
"linear-gradient(270deg, rgba(255, 255, 255, 0) 0%, rgba(18, 18, 18, 0.8) 100%)",
split:
"linear-gradient(270deg, rgba(255, 255, 255, 0) 0%, rgba(18, 18, 18, 0.8) 100%)",
overlay:
"linear-gradient(270deg, rgba(255, 255, 255, 0) 0%, rgba(18, 18, 18, 0.8) 100%)",
},
},
};
FuWidget.init({
// ... other config
themeOptions: themeOptions,
});Content Labels
Custom labels can be set for each content piece from the Management Portal under the Labels configuration.
Prize Labels
prizeCardTitle— Title of the prize cardprizeCardDescription— Description in the prize cardprizeCta— Call to action for prize details
Lead Form Labels
leadTitle— Title of the lead formleadDescription— Description text for lead collectionleadCta— Submit button textleadSuccessTitle— Success message titleleadSuccessDescription— Success message description
Score Message Labels
scoreMessagePerfect— Override the default message when a user gets a perfect score.scoreMessageAmazing— Override the default message when a user gets 80+%.scoreMessageWellDone— Override the default message when a user gets 60+%.scoreMessageGoodEffort— Override the default message when a user gets 40+%.scoreMessageKeepGoing— Override the default message when a user gets less than 40%.
Localization
The widget supports multiple languages for both the UI and content, set independently:
language— Controls the widget UI languagefansUnited.lang— Controls the content language from the Fans United API
FuWidget.init({
language: "en", // UI language
fansUnited: {
lang: "en", // Content language
// ... other config
},
});Available UI Languages (language)
ar— Arabic (right-to-left)bg— Bulgarianen— Englishro— Romanianpt— Portuguesesr— Serbianes— Spanishfr— Frenchde— Germanit— Italianfr-be— Belgian Frenchpl— Polishpt-br— Brazilian Portuguesesk— Slovakel— Greek
Available Content Languages (fansUnited.lang)
ar— Arabicbg— Bulgarianen— Englishro— Romanianel— Greeksk— Slovakpt— Portuguesesr— Serbianhu— Hungariansv— Swedishes— Spanishfr— Frenchnl— Dutchde— Germanit— Italian
Right-to-Left (Arabic)
Setting language: "ar" switches every widget to right-to-left. There is nothing else to configure:
- Each widget mirrors its own layout — text alignment, image and control order, carousels, drawers and overlays.
- Numbers that must stay readable left-to-right (scores, points, kickoff times) are isolated so a right-to-left paragraph cannot reorder them.
- A
dir="rtl"attribute is set on the widget's root element, so any surrounding markup inherits the direction too. - Because neither Ubuntu nor Roboto carries Arabic glyphs,
Noto Sans Arabicis appended as a fallback. AcustomFontFamilyyou configure still wins for the glyphs it actually has.
language and fansUnited.lang remain independent — set both to ar for an Arabic interface and Arabic content.
All-Widget Language Support
The FU Widget system supports multiple languages for all widget types. Each widget displays content in the language specified in the configuration. All widgets support the following languages:
- English (en)
- Arabic (ar) — right-to-left
- Bulgarian (bg)
- Romanian (ro)
- Portuguese (pt)
- Serbian (sr)
- Spanish (es)
- French (fr)
- German (de)
- Italian (it)
- Belgian French (fr-be)
- Polish (pl)
- Brazilian Portuguese (pt-br)
- Slovak (sk)
- Greek (el)
Options Layout
The optionsLayout configuration controls how answer options are displayed in widgets that support multiple choice answers (Classic Quiz, Personality Quiz, Poll, Event Game).
Available Layout Options
twoByTwo— Displays answer options in a 2x2 grid layout (2 columns). Best for widgets with 4 or fewer options.row— Displays answer options in a single row (horizontal layout). Best for 2–3 short options.column— Displays answer options in a single column (vertical layout, one per row). Best for longer option text or many options.
Global Configuration
Set the default layout for all widgets:
FuWidget.init({
// ... other config
optionsLayout: "column", // or "twoByTwo" or "row"
});Per-Widget Configuration
Override the global setting for individual widgets:
<div
data-component="fu-widget"
data-content-type="classic-quiz"
data-content-id="your-quiz-id"
data-options-layout="twoByTwo"
></div>Supported Widgets
The following widgets support the optionsLayout configuration:
- Classic Quiz
- Personality Quiz
- Poll
- Event Game
Display Configuration
The widget supports the following display configuration options:
FuWidget.init({
// ... other config
displayPlayNextSection: true, // Show/hide "Play Next" section (default: true)
displayQuizDescription: true, // Show/hide quiz description (default: true)
defaultImagePlaceholderUrl: "https://example.com/placeholder.jpg", // Default placeholder image URL
});displayPlayNextSection— Controls the visibility of the "Play Next" section in quiz widgets (default:true)displayQuizDescription— Controls the visibility of quiz descriptions (default:true)defaultImagePlaceholderUrl— An optional URL for the default placeholder image used when content images fail to load or are not available
These options can also be set on individual widget instances:
<div
data-component="fu-widget"
data-content-type="classic-quiz"
data-content-id="quiz-123"
data-display-play-next-section="false"
data-display-quiz-description="false"
data-default-image-placeholder-url="https://example.com/custom-placeholder.jpg"
></div>URL Configuration
The widget supports configuring URLs for different content types. These URLs are used when a user clicks an item in a List widget.
FuWidget.init({
// ... other config
urls: {
classicQuizUrl: "/quiz/{CONTENT_ID}",
personalityQuizUrl: "/personality-quiz/{CONTENT_ID}",
eitherOrUrl: "/either-or/{CONTENT_ID}",
pollUrl: "/poll/{CONTENT_ID}",
matchQuizUrl: "/match-quiz/{CONTENT_ID}",
eventGameUrl: "/event-game/{CONTENT_ID}",
},
});URL Pattern Keys
urls.classicQuizUrl— URL pattern for quiz contenturls.personalityQuizUrl— URL pattern for personality quiz contenturls.eitherOrUrl— URL pattern for either/or gamesurls.pollUrl— URL pattern for pollsurls.matchQuizUrl— URL pattern for match quiz gamesurls.eventGameUrl— URL pattern for event games
Each URL pattern should include the {CONTENT_ID} placeholder, which is replaced with the actual content ID when generating links.
The same patterns can be set under the
listconfig key, and overridden per-widget on the List widget withdata-*-urlattributes. See Widgets → List.
SPA Refresh
For Single Page Applications (SPAs) or dynamic content scenarios where widgets need to be reinitialized after navigation or content changes, use the refresh method:
// Manually refresh all widgets on the page
FuWidget.refresh();When to Use Widget Refresh
- SPA Navigation: After navigating to a new page that contains widgets
- Dynamic Content Loading: When new widget elements are added to the DOM via JavaScript
- Content Updates: When widget content IDs or configurations change dynamically
- Error Recovery: To reinitialize widgets after network errors or other issues
Implementation Example
// Example for SPA router
router.on("route-changed", () => {
// Small delay to ensure DOM is updated
setTimeout(() => {
FuWidget.refresh();
}, 100);
});
// Example for dynamic content loading
function loadNewContent() {
fetch("/api/content")
.then((response) => response.text())
.then((html) => {
document.getElementById("content").innerHTML = html;
// Refresh widgets after new content is loaded
FuWidget.refresh();
});
}Important Notes
- The refresh method will reinitialize ALL widgets on the page.
- Existing widget state will be lost during refresh.
- Only call refresh when necessary to avoid performance issues.
- Widgets automatically prevent double initialization, so calling refresh multiple times is safe.
Updated 4 days ago
