Top X
Season-long competitive picks predictor with a multi-tab interface: Play, Leaderboard, Private Leagues, Rules, and Prizes. Users pick their top performers for each gameweek (e.g. top 3 scorers) from a curated fixture list. Includes game navigation label control, consent gating, custom Play-tab banners, and first-class Betslip integration.
Import
import { TopX } from "fansunited-frontend-components";
import {
TopXProps,
TopXTab,
TopXGameNavLabel,
TopXBetslipConfig,
PlayTabBanner,
ConsentDef,
} from "fansunited-frontend-core";Required props
| Prop | Type | Description |
|---|---|---|
entityId | string | Top X template identifier. |
sdk | FansUnitedSDKModel | SDK instance. |
language | LanguageType | Display language. |
TopX does not take a
templateprop — its layout is a fixed multi-tab interface.
Optional props
| Prop | Type | Default | Description |
|---|---|---|---|
themeOptions | CustomThemeOptions | — | See Theming. |
userIsLoggedIn | boolean | false | Host auth state. |
signInCTA | SignInCTADetails | — | See Sign-in CTA. |
tabs | TopXTab[] | all tabs | Which tabs to enable. |
defaultImagePlaceholderUrl | string | — | Fallback image URL for the hero header. |
playTabBanners | PlayTabBanner[] | — | Custom banners injected into the Play tab. |
consents | ConsentDef[] | — | Consent definitions required before predicting. |
matchCardBgImageUrl | string | — | Background image URL for match prediction cards. |
showOdds | boolean | false | Render betting odds on the match cards. See Odds below. |
odds | Omit<OddsConfig, "market"> | — | Operator preference, poll interval, flash mode and affiliate ids. See Odds below. |
operatorId | string | — | Deprecated — use odds.operators. Still honoured when odds.operators is unset. |
betslip | TopXBetslipConfig | — | Betslip integration. |
showCountdown | boolean | true | Show predictions-close countdown on open games. |
gameNavLabel | TopXGameNavLabel | "gameWeek" | How each game is labelled in the navigation header. |
Tabs
type TopXTab = "play" | "leaderboard" | "private-leagues" | "rules" | "prizes";| Tab | Description |
|---|---|
play | Main prediction interface — submit top picks for each game. |
leaderboard | Global rankings with per-game scope picker for settled games. |
private-leagues | Create and join private leagues. |
rules | Searchable game rules and scoring system. |
prizes | Prize distribution and rewards. |
Pass tabs to limit the visible tabs:
<TopX {...otherProps} tabs={["play", "leaderboard"]} />Game navigation label
The Play tab shows a navigation header with previous/next arrows so users can move between games. gameNavLabel controls what appears as the title in that header.
type TopXGameNavLabel = "gameWeek" | "title" | "titleAndDescription";| Value | What is shown | Falls back to |
|---|---|---|
"gameWeek" (default) | Localised "Gameweek 1", "Gameweek 2"… The index is 1-based and reflects the game's position in the template order. Fully localised — e.g. Spielwoche 3 (de), Jornada 3 (es), Giornata 3 (it). | — |
"title" | The game's title field from the API. No description shown. | Localised gameweek label when the game has no title. |
"titleAndDescription" | The game's title field, and its description field (HTML supported) rendered below the title when present. | Localised gameweek label when the game has no title. |
When to use each value:
"gameWeek"— use when games don't carry meaningful API titles, or when you want consistent localised labels across all clients regardless of API data quality."title"— use when the API data contains curated game names (e.g. "Matchday 33 — Title Race Decider") and you want to surface them, but the description is not needed."titleAndDescription"— use when games carry editorial descriptions that add context, such as prize information or special rules for that round.
// Default — "Gameweek 1", "Gameweek 2"… (no prop needed)
<TopX entityId="topx-123" sdk={sdk} language="en" />
// Show the game's API title only
<TopX entityId="topx-123" sdk={sdk} language="en" gameNavLabel="title" />
// Show the game's API title and description
<TopX entityId="topx-123" sdk={sdk} language="en" gameNavLabel="titleAndDescription" />Play tab banners
Inject custom React content into specific slots inside the Play tab.
type PlayTabBannerPosition =
| "above-group-nav"
| "below-group-nav"
| "below-matches"
| "bottom"
| "left"
| "right";
interface PlayTabBanner {
position: PlayTabBannerPosition;
render: () => React.ReactNode;
}<TopX
{...otherProps}
playTabBanners={[
{ position: "above-group-nav", render: () => <SponsorBanner /> },
{ position: "right", render: () => <AdSlot id="topx-sidebar" /> },
]}
/>Consent gating
interface ConsentDef {
consentId: string;
body: string; // HTML allowed — links, formatting
required?: boolean; // default false
defaultChecked?: boolean; // default false
position?: "before" | "after"; // ClassicQuizPlay only — ignored here
}- Required consents block prediction submission until accepted.
- Optional consents appear in the same modal but do not block progression.
- Once accepted, the modal does not reappear for that user.
- The same shape is shared with
ClassicQuizPlay— see Consents for the full reference, including the{{privacyPolicyUrl}}/{{termsAndConditionsUrl}}body tokens.
<TopX
{...otherProps}
consents={[
{
consentId: "tos",
body: 'I accept the <a href="https://example.com/tos" target="_blank">Terms of Service</a>.',
required: true,
defaultChecked: false,
},
]}
/>Odds
Odds are off by default. With showOdds set, each upcoming match card shows the full-time 1X2 price that its predicted score implies, from one operator. This applies in the Play tab and in a league member's predictions. Odds refresh every odds.pollingInterval and fail silently: when the odds API has no prices for a match, the card simply shows none.
odds takes the same settings as Predictor's: operators, pollingInterval, mode, flashInterval, affiliateId and affiliateIds.
showOdds: while it's false (the default), the odds request is skipped altogether. A betslip withtrigger: "odds-only"is fed by clicking those odds, so that trigger needsshowOddsset.odds.operatorsis a preference order. Each match shows the first operator that prices it completely. Without it, each match shows whichever operator the API lists first.
<TopX entityId="..." sdk={sdk} language="en" showOdds odds={{ operators: ["betano-bg"] }} />The card and the betslip show the same operator unless you set them to different values:
| You set | Card odds | Betslip |
|---|---|---|
odds.operators | first preferred | first of odds.operators |
betslip.operatorId | betslip.operatorId | betslip.operatorId |
| Both | odds.operators | betslip.operatorId |
The deprecated operatorId still works and stands in for odds.operators: [operatorId].
See Choosing a bookmaker on the Betslip page for the list of available operator ids.
Betslip integration
When the betslip prop is provided, TopX renders a Betslip widget internally as a sibling — you do not add a separate <Betslip /> component to the page. See the Betslip component page for the standalone widget reference.
interface TopXBetslipConfig {
trigger?: TopXBetslipTrigger; // "predictions-only" | "odds-only"
position?: BetslipPosition;
maxSelections?: number;
stakePresets?: number[];
oddsPollingInterval?: number;
currency?: string;
ctaUrlTemplate?: string;
brandingLogoUrl?: string;
operatorId?: string; // falls back to the first of odds.operators — see Odds
affiliateId?: string;
labels?: BetslipLabels;
themeOptions?: CustomThemeOptions;
}Trigger modes
| Mode | When selections are sent to Betslip |
|---|---|
"predictions-only" (default) | Every time the user edits a pick. |
"odds-only" | Only when the user clicks an odds button on the post-submit odds display. Requires showOdds. |
Theme inheritance
If betslip.themeOptions is not provided, the Betslip widget inherits the TopX themeOptions. A single theme on TopX is enough to style both consistently.
Examples
Basic
<TopX
entityId="topx-template-123"
sdk={sdk}
language="en"
/>Authenticated with selected tabs
<TopX
entityId="topx-template-123"
sdk={sdk}
language="en"
userIsLoggedIn
tabs={["play", "leaderboard", "private-leagues"]}
matchCardBgImageUrl="https://your-cdn.com/stadium-bg.jpg"
themeOptions={{ mode: "dark" }}
/>Custom game navigation label
// Games have editorial titles and descriptions in the API
<TopX
entityId="topx-template-123"
sdk={sdk}
language="en"
gameNavLabel="titleAndDescription"
/>Full setup with banners and consents
<TopX
entityId="topx-template-123"
sdk={sdk}
language="en"
userIsLoggedIn={false}
signInCTA={{ defaultLabel: "Sign in to Predict", onClick: handleSignIn }}
tabs={["play", "leaderboard", "private-leagues", "rules", "prizes"]}
gameNavLabel="titleAndDescription"
showCountdown
matchCardBgImageUrl="https://your-cdn.com/matchcard-bg.jpg"
playTabBanners={[
{ position: "above-group-nav", render: () => <SponsorBanner /> },
{ position: "right", render: () => <AdSlot id="topx-sidebar" /> },
]}
consents={[
{
consentId: "tos",
body: 'I accept the <a href="https://example.com/tos" target="_blank">Terms of Service</a>.',
required: true,
defaultChecked: false,
},
]}
themeOptions={{ mode: "light" }}
/>TopX + Betslip
<TopX
entityId="topx-template-123"
sdk={sdk}
language="en"
userIsLoggedIn
betslip={{
trigger: "predictions-only",
position: "side-right",
currency: "£",
stakePresets: [5, 10, 25, 50],
ctaUrlTemplate:
"https://your-bookmaker.com/bet?ids={selectionIds}&stake={stake}&ref={currentUrl}",
labels: { disclaimer: "18+ | Please gamble responsibly" },
}}
themeOptions={{ mode: "dark" }}
/>Updated 6 days ago
