Betslip

Floating betslip component that collects selections from any external source and routes users to a bookmaker via a configurable deep-link URL. Communicates with the rest of the page through a singleton command bus (betslipApi), so it decouples cleanly from host-page code.

For a deeper look at the command bus (setSelection, removeSelection, getState, subscribe, pre-mount queuing), see the Betslip Command Bus page.

Import

import { Betslip, betslipApi } from "fansunited-frontend-components";
import {
  BetslipProps,
  BetslipLabels,
  BetslipPosition,
} from "fansunited-frontend-core";

Important: Always import betslipApi from fansunited-frontend-components. The widget bundles its own singleton instance — importing from the components package guarantees your code and the widget share the same bus.

Required props

PropTypeDescription
sdkFansUnitedSDKModelSDK instance.
languageLanguageTypeDisplay language.

The Betslip has no entityId — selections are pushed in from external code via the command bus.

Optional props

PropTypeDefaultDescription
positionBetslipPosition"bottom-right"Viewport anchor. See below.
maxSelectionsnumber20Hard cap on simultaneous selections.
stakePresetsnumber[][5, 10, 20]Quick-select stake buttons in the footer.
oddsPollingIntervalnumber30000Odds refresh interval in milliseconds.
currencystring"€"Currency symbol next to stake and potential win.
ctaUrlTemplatestringBookmaker deep-link URL template (see below).
brandingLogoUrlstringOperator logo URL that overrides the bookmaker logo.
labelsBetslipLabelsUI text overrides.
themeOptionsCustomThemeOptionsTheme tokens (see Theming and Theming section below).

Positions

type BetslipPosition =
  | "bottom-right"
  | "bottom-left"
  | "top-right"
  | "top-left"
  | "side-right"
  | "side-left";

Corner positions (bottom-*, top-*)

  • 320 px wide, fixed 16 px from the viewport edges.
  • Header shows an expand/collapse chevron.
  • Collapsed: compact summary (combined odds, stake, potential win).
  • Expanded: full selection list (scrollable, max 300 px), plus stake/CTA footer.

Side positions (side-*)

  • 340 px wide, full viewport height (100 vh).
  • A 32 × 64 px tab handle peeks out at 33% from the top when closed; shows the bookmaker/branding logo rotated 90°, or three grip lines when no logo is available.
  • A selection count badge appears on the closed tab when maxSelections is set.
  • 0.3 s slide-in/out transition.

CTA URL template

ctaUrlTemplate is the bookmaker deep-link. Placeholders are replaced at click time with live values from the current betslip state.

ctaUrlTemplate="https://your-bookmaker.com/mybet/?selection_ids={selectionIds}&stake={stake}&return_url={currentUrl}"

Available placeholders

PlaceholderValue
{selectionIds}Comma-separated SDK selection.id values (up to the first 10).
{stake}Current stake input value (float).
{currentUrl}window.location.href, URI-encoded.
{eventId}eventId of the first selection.
{oddsDecimal}Decimal odds of the first selection.
{market}Market code of the first selection.
{outcome}Outcome label of the first selection.

Theming

The Betslip derives its primary accent color from a three-level priority chain:

  1. Custom primarythemeOptions.colorSchemes[mode].palette.primary.plainColor.
  2. Bookmaker colorbookmaker.branding.backgroundColor (or bookmaker.assets[0].backgroundColor) from the SDK odds response.
  3. Default theme primary.

You get bookmaker brand colors for free when odds are available, and can override them via themeOptions.

Note: When neither a custom primary nor bookmaker odds are present, the widget falls back to the default theme primary (#1A77D2).

Labels

BetslipLabels lets you override any piece of UI text without switching languages — including individual outcome labels and market display names.

import { BetslipLabels } from "fansunited-frontend-core";

const labels: BetslipLabels = {
  title: "My Betslip",
  placeBetLabel: "Place Bet",
  emptyTitle: "Your betslip is empty",
  emptyDescription: "Add selections to get started",
  continueLabel: "Continue",
  disclaimer: "18+ | Gamble responsibly",
  combinedOddsLabel: "Combined Odds",
  stakeLabel: "Stake",
  totalStakeLabel: "Total Stake",
  potentialWinLabel: "Potential Win",
  oddsUnavailableLabel: "Odds N/A",
  suspendedLabel: "Suspended",
  outcomeLabels: {
    yes: "Over",
    no: "Under",
    draw: "Draw",
    or: "/",
    nobody: "No Scorer",
    noGoal: "No Goal",
  },
  marketLabels: {
    FT_1X2: "Full Time",
    DOUBLE_CHANCE: "Double Chance",
    OVER_GOALS_2_5: "Over/Under 2.5 Goals",
  },
};

Conflict detection

The betslip automatically detects mutually exclusive selections — two selections with the same eventId and market. When conflicts are detected:

  • A warning banner appears at the top of the expanded selection list.
  • Conflicting selection cards are highlighted with danger styling.
  • The CTA button is disabled until the conflict is resolved.

Conflicts resolve automatically when the user removes one of the conflicting selections.

Odds behavior

BehaviorDetail
PollingOdds are fetched from sdk.odds.getByMatchIds() on mount and refreshed every oddsPollingInterval ms (default 30 s).
Movement indicatorGreen ▲ or red ▼ shown when the bookmaker response includes movement: "UP" | "DOWN". No indicator when the field is absent.
UnavailableWhen no odds are returned for a selection, the card shows oddsUnavailableLabel and the CTA is disabled.
SuspendedSelections with status "suspended" are highlighted and block the CTA.
Settled statesCards display ✓ (won), ✗ (lost), or ≈ (void) once a selection is settled.

Over/Under markets: Use "yes" for Over and "no" for Under in the selection ID (not "over"/"under"). The widget handles label display internally.

Correct Score market: Outcomes use hyphen notation, e.g. "1-2". The widget parses both - and : as separators.

Predictor integration

The Predictor component has first-class Betslip support. When the betslip prop is configured on Predictor, the Betslip widget is rendered internally — no separate <Betslip /> is needed. See the Predictor page.

Examples

Quick start

import { Betslip, betslipApi } from "fansunited-frontend-components";

<>
  <Betslip
    sdk={sdk}
    language="en"
    position="bottom-right"
    ctaUrlTemplate="https://your-bookmaker.com/betslip?selections={selectionIds}&stake={stake}"
  />

  <button onClick={() => betslipApi.setSelection("fb:m:451678:FT_1X2:1")}>
    Back Home Win
  </button>
</>

Side panel with custom branding

<Betslip
  sdk={sdk}
  language="en"
  position="side-right"
  maxSelections={10}
  stakePresets={[5, 10, 25, 50]}
  currency="£"
  brandingLogoUrl="https://your-cdn.com/logo.png"
  ctaUrlTemplate="https://your-bookmaker.com/bet?ids={selectionIds}&stake={stake}&ref={currentUrl}"
  labels={{
    title: "Your Selections",
    placeBetLabel: "Bet Now",
    disclaimer: "18+ | Please gamble responsibly | BeGambleAware.org",
  }}
  themeOptions={{
    mode: "dark",
    colorSchemes: {
      dark: {
        palette: {
          primary: {
            plainColor: "#FF5722",
            primaryContainer: "#FF7043",
            onPrimary: "#FFFFFF",
          },
        },
        surface: "#1A1A1A",
        textPrimary: "#FAFAFA",
      },
    },
  }}
/>

Programmatic selection management

import { betslipApi } from "fansunited-frontend-components";

// Add selections — safe to call before the widget mounts (pre-mount queuing)
betslipApi.setSelection("fb:m:451678:FT_1X2:1");
betslipApi.setSelection("fb:m:451679:DOUBLE_CHANCE:1x");

// Replace an outcome for the same event+market
betslipApi.setSelection("fb:m:451678:FT_1X2:2"); // replaces the home-win

// Remove a specific selection
betslipApi.removeSelection("fb:m:451679:DOUBLE_CHANCE:1x");

See Betslip Command Bus for the full API including getState() and subscribe().