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
betslipApifromfansunited-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
| Prop | Type | Description |
|---|---|---|
sdk | FansUnitedSDKModel | SDK instance. |
language | LanguageType | Display language. |
The Betslip has no
entityId— selections are pushed in from external code via the command bus.
Optional props
| Prop | Type | Default | Description |
|---|---|---|---|
position | BetslipPosition | "bottom-right" | Viewport anchor. See below. |
maxSelections | number | 20 | Hard cap on simultaneous selections. |
stakePresets | number[] | [5, 10, 20] | Quick-select stake buttons in the footer. |
oddsPollingInterval | number | 30000 | Odds refresh interval in milliseconds. |
currency | string | "€" | Currency symbol next to stake and potential win. |
ctaUrlTemplate | string | — | Bookmaker deep-link URL template (see below). |
brandingLogoUrl | string | — | Operator logo URL that overrides the bookmaker logo. |
labels | BetslipLabels | — | UI text overrides. |
themeOptions | CustomThemeOptions | — | Theme 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-*)
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-*)
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
maxSelectionsis 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
| Placeholder | Value |
|---|---|
{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:
- Custom primary —
themeOptions.colorSchemes[mode].palette.primary.plainColor. - Bookmaker color —
bookmaker.branding.backgroundColor(orbookmaker.assets[0].backgroundColor) from the SDK odds response. - 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
| Behavior | Detail |
|---|---|
| Polling | Odds are fetched from sdk.odds.getByMatchIds() on mount and refreshed every oddsPollingInterval ms (default 30 s). |
| Movement indicator | Green ▲ or red ▼ shown when the bookmaker response includes movement: "UP" | "DOWN". No indicator when the field is absent. |
| Unavailable | When no odds are returned for a selection, the card shows oddsUnavailableLabel and the CTA is disabled. |
| Suspended | Selections with status "suspended" are highlighted and block the CTA. |
| Settled states | Cards 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().
