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

PropTypeDescription
entityIdstringTop X template identifier.
sdkFansUnitedSDKModelSDK instance.
languageLanguageTypeDisplay language.

TopX does not take a template prop — its layout is a fixed multi-tab interface.

Optional props

PropTypeDefaultDescription
themeOptionsCustomThemeOptionsSee Theming.
userIsLoggedInbooleanfalseHost auth state.
signInCTASignInCTADetailsSee Sign-in CTA.
tabsTopXTab[]all tabsWhich tabs to enable.
defaultImagePlaceholderUrlstringFallback image URL for the hero header.
playTabBannersPlayTabBanner[]Custom banners injected into the Play tab.
consentsConsentDef[]Consent definitions required before predicting.
matchCardBgImageUrlstringBackground image URL for match prediction cards.
betslipTopXBetslipConfigBetslip integration. See below.
showCountdownbooleantrueShow predictions-close countdown on open games.
gameNavLabelTopXGameNavLabel"gameWeek"How each game is labelled in the navigation header. See below.

Tabs

type TopXTab = "play" | "leaderboard" | "private-leagues" | "rules" | "prizes";
TabDescription
playMain prediction interface — submit top picks for each game.
leaderboardGlobal rankings with per-game scope picker for settled games.
private-leaguesCreate and join private leagues.
rulesSearchable game rules and scoring system.
prizesPrize 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";
ValueWhat is shownFalls 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;
  defaultChecked: boolean;
}
  • 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.
<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,
    },
  ]}
/>

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;
  labels?: BetslipLabels;
  themeOptions?: CustomThemeOptions;
}

Trigger modes

ModeWhen 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.

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" }}
/>

Multi-game football score-prediction tournament with a multi-tab interface: Play, Leaderboard, Private Leagues, Rules, and Prizes. Matches are grouped into discrete games each with its own cutoff, golden-goal tiebreaker, and settlement. Includes consent gating, custom Play-tab banners, countdown timer, and first-class Betslip integration.

Import

import { TopX } from "fansunited-frontend-components";
import {
  TopXProps,
  TopXTab,
  TopXBetslipConfig,
  PlayTabBanner,
  ConsentDef,
} from "fansunited-frontend-core";

Required props

PropTypeDescription
entityIdstringTopX template identifier.
sdkFansUnitedSDKModelSDK instance.
languageLanguageTypeDisplay language.

TopX does not take a template prop — its layout is a fixed multi-tab interface.

Optional props

PropTypeDefaultDescription
themeOptionsCustomThemeOptionsSee Theming.
userIsLoggedInbooleanfalseHost auth state.
signInCTASignInCTADetailsSee Sign-in CTA.
tabsTopXTab[]all tabsWhich tabs to enable.
defaultImagePlaceholderUrlstringFallback image URL for the hero header.
playTabBannersPlayTabBanner[]Custom banners injected into the Play tab.
consentsConsentDef[]Consent definitions required before predicting.
matchCardBgImageUrlstringBackground image URL for match prediction cards.
showCountdownbooleantrueShow the kickoff countdown timer on match cards for open games.
betslipTopXBetslipConfigBetslip integration. See below.

Tabs

type TopXTab = "play" | "leaderboard" | "private-leagues" | "rules" | "prizes";
TabDescription
playMain prediction interface — browse games by day and submit score forecasts.
leaderboardGlobal rankings with pagination and user highlight.
private-leaguesCreate and join private leagues.
rulesSearchable game rules and scoring system.
prizesPrize distribution and rewards.

Pass tabs to limit the visible tabs:

<TopX {...otherProps} tabs={["play", "leaderboard"]} />

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;
}
PositionLocation
above-group-navAbove the game-day navigation.
below-group-navBelow the game-day navigation.
below-matchesBelow the list of match cards.
bottomVery bottom of the Play tab.
leftLeft sidebar (desktop only).
rightRight sidebar (desktop only).
<TopX
  {...otherProps}
  playTabBanners={[
    { position: "above-group-nav", render: () => <SponsorBanner /> },
    { position: "right", render: () => <AdSlot id="top-x-sidebar" /> },
  ]}
/>

Countdown timer

When showCountdown is true (the default), each match card displays a live kickoff countdown while the game is open. Set to false to hide it — only the kickoff time will be shown.

<TopX {...otherProps} showCountdown={false} />

Consent gating

interface ConsentDef {
  consentId: string;
  body: string;        // HTML allowed — links, formatting
  required: boolean;
  defaultChecked: boolean;
}
  • 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.
<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,
    },
  ]}
/>

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;
  labels?: BetslipLabels;
  themeOptions?: CustomThemeOptions;
}

Trigger modes

ModeWhen selections are sent to Betslip
"predictions-only" (default)Every time the user edits a score (home/away increment or decrement). Outcome derivation: "1" (home win), "X" (draw), "2" (away win).
"odds-only"Only when the user clicks an odds button on the post-submit odds display.

Theme inheritance

If betslip.themeOptions is not provided, the Betslip widget inherits TopX's own themeOptions. A single theme on TopX is enough to style both consistently.

Examples

Basic

<TopX
  entityId="top-x-template-123"
  sdk={sdk}
  language="en"
/>

Authenticated with selected tabs

<TopX
  entityId="top-x-template-123"
  sdk={sdk}
  language="en"
  userIsLoggedIn
  tabs={["play", "leaderboard", "private-leagues"]}
  matchCardBgImageUrl="https://your-cdn.com/stadium-bg.jpg"
  themeOptions={{ mode: "dark" }}
/>

Full setup with banners and consents

<TopX
  entityId="top-x-template-123"
  sdk={sdk}
  language="en"
  userIsLoggedIn={false}
  signInCTA={{ defaultLabel: "Sign in to Predict", onClick: handleSignIn }}
  tabs={["play", "leaderboard", "private-leagues", "rules", "prizes"]}
  matchCardBgImageUrl="https://your-cdn.com/matchcard-bg.jpg"
  showCountdown={true}
  playTabBanners={[
    { position: "above-group-nav", render: () => <SponsorBanner /> },
    { position: "right", render: () => <AdSlot id="top-x-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 (predictions trigger)

<TopX
  entityId="top-x-template-123"
  sdk={sdk}
  language="en"
  userIsLoggedIn
  tabs={["play", "leaderboard"]}
  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" }}
/>

TopX + Betslip (odds trigger)

<TopX
  entityId="top-x-template-123"
  sdk={sdk}
  language="en"
  userIsLoggedIn
  betslip={{
    trigger: "odds-only",
    position: "bottom-right",
    currency: "€",
    ctaUrlTemplate:
      "https://your-bookmaker.com/bet?ids={selectionIds}&stake={stake}",
  }}
/>