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 | — | Overrides the CTA link. Unset, the operator's own betslip deep link is used (see below). |
brandingLogoUrl | string | — | Operator logo URL that overrides the bookmaker logo. |
operatorId | string | — | Operator config id to request odds for, e.g. "betano-bg" (see below). |
affiliateId | string | — | Affiliate id substituted into the operator's URLs (see below). |
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.
The CTA link
The CTA resolves in three steps, first match wins:
ctaUrlTemplate, if you set one — your own destination.- The operator's betslip deep link from the odds response (
operator.betslip_url_template), picked for the current device's app type. This is the one that lands the user on a pre-filled betslip at the bookmaker, and it needs no configuration. - The operator's homepage — a last resort that opens the bookmaker with an empty slip.
So in the normal case you configure nothing. Set ctaUrlTemplate only when you need the CTA to go somewhere other than the operator's betslip.
How the operator link is filled
The API resolves every placeholder in its own template except {betslip_ids}, which is left for the widget: it becomes the operator's selection ids (operator_info.selection_id) for the current selections, joined with the operator's own betslip_id_separator.
template https://www.betano.bg/mybet/?selection_ids={betslip_ids}&aff=
result https://www.betano.bg/mybet/?selection_ids=10184348244,10184348269&aff=
Affiliate attribution
Operator URLs carry an {affiliate_id} placeholder that the API fills only when the request supplies one — otherwise the tag comes back empty, as in the &aff= above. Set affiliateId so clicks through the CTA are attributed to you:
<Betslip sdk={sdk} language="en" operatorId="betano-bg" affiliateId="your-affiliate-id" />Available placeholders
These work in ctaUrlTemplate, and in an operator template should one carry them.
| Placeholder | Value |
|---|---|
{betslip_ids} | Operator selection ids joined with the operator's separator. The operator template's own placeholder. |
{selectionIds} | Comma-separated selection ids, URI-encoded as a whole. |
{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. |
Choosing a bookmaker (operatorId)
operatorId)sdk.odds.getByMatchIds() returns odds from every bookmaker the backend has configured for a match. The betslip renders one bookmaker at a time, and without operatorId it uses whichever one the response lists first — which is not guaranteed to stay the same.
Set operatorId to the operator config id to pin the betslip to that bookmaker:
<Betslip sdk={sdk} language="en" operatorId="betano-bg" />It is passed to the odds request as the SDK's operators filter, so the response is narrowed server-side rather than in the widget — the betslip never downloads the other bookmakers' prices.
Everything the betslip derives from the bookmaker follows — the odds themselves, the accent color, the header and CTA logo, and the deep links. Markets the chosen bookmaker does not price are dropped from the betslip (see Odds behavior below), and if it prices nothing in the betslip, the panel stays hidden.
Available operator ids
betano-bg
efbet-bg
inbet-bg-online
inbet-bg-offline
betwild-bg
bet-bg
sesame-bg
winbet-bg
stoiximan-gr
Operators are configured on the Fans United side, so this list can change — check with your account manager if the one you need is not here.
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 | A selection the bookmaker does not price at all is dropped from the betslip once odds arrive, with a console.warn naming it — the bus accepts any selection id, so this is the first point at which it is knowable. While odds are still in flight, or when the event has no odds from anyone, 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 the Betslip Command Bus page for the full API including getState() and subscribe().
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().
Updated 13 days ago
