Competition Statistics
A competition's season at a glance: a progress header, upcoming fixtures and results grouped by stage and round, player rankings, and season-wide statistics. Expanding a finished or live match shows its match stats and a goal timeline. Expanding an upcoming one shows both teams' form, a season comparison and the previous meeting this season. Odds, when enabled, sit on the match row itself.
All data comes from the Sports API's competition-by-id endpoint, the same one Standings reads. Names, crests, headshots and stage names come from the Search API in batches of 100 ids, so a league with 400+ players and teams costs a handful of parallel requests.
Import
import { CompetitionStatistics } from "fansunited-frontend-components";
import { CompetitionStatisticsProps } from "fansunited-frontend-core";Required props
| Prop | Type | Description |
|---|---|---|
entityId | string | Competition ID, e.g. "fb:c:3". |
sdk | FansUnitedSDKModel | SDK instance. |
language | LanguageType | Display language. Entity names are localized from the Search API's translations. |
Optional props
| Prop | Type | Description |
|---|---|---|
themeOptions | CustomThemeOptions | See Theming. |
seasonId | string | Season override, e.g. "fb:c:3:2026/27". Defaults to the competition's current season. |
stageId | string | Scope matches and team statistics to one stage. Defaults to every stage. |
sections | CompetitionStatisticsSection[] | Which sections render, in the order given: "header", "matches", "players", "season". Defaults to all four in that order. A repeated key renders once. |
playerRankingsLayout | "auto" | "grid" | "tabs" | How the player rankings are laid out. "auto" (default) shows a grid of cards and switches to tabs when the container is narrower than 600px. |
playerRankingsRows | number | Rows per player ranking before "Show all". Defaults to 5; clamped to 1–10. |
showOdds | boolean | Show odds on each upcoming and live match row. Defaults to false. See Odds. |
odds | OddsConfig | mode ("default" / "flash"), flashInterval, pollingInterval, operators, affiliateId, affiliateIds. The same config as CompetitorCard's odds. |
callbacks | CompetitionStatisticsCallbacks | onAddToBetslip(selection), see Odds; onBroadcastClick(matchId), see TV broadcasts. See also Callbacks. |
liveRefresh | boolean | Poll live matches so their score, minute and stats stay current. Defaults to true. |
refreshInterval | number | Live poll interval in ms. Defaults to 30000. |
matchCTA | CompetitionStatisticsLinkCTA | Where the "highest-scoring match" and "biggest win" cards link to. |
teamCTA | CompetitionStatisticsLinkCTA | Where the team callouts (most wins, best attack, …) link to. |
playerCTA | CompetitionStatisticsLinkCTA | Where a player in the rankings links to. |
labels | CompetitionStatisticsLabels | Overrides for any user-facing string. See Labels. |
Sections
| Section | Contents |
|---|---|
header | Competition crest and name, season, season dates, the next round, and how many matches have been played. |
matches | Upcoming / Results tabs, a stage picker (multi-stage competitions only), a round picker, and matches grouped by day. Rows expand. |
players | Top scorers, assists, goals + assists, clean sheets, most minutes and most cards. Up to 10 players each; tied ranks read 2=. |
season | Played / goals / home-win / both-teams-scored tiles, result split, goal timing by 15-minute period, goal lines (2+ / 3+ / 4+ goals), averages per match, total cards, team callouts and the two record matches. |
Player rankings are hidden when the season has no player statistics yet.
Matches
Stages. In a competition with more than one stage — a regular season followed by championship and relegation groups, or a play-off — a row of stage pills sits above the round picker, styled like the Standings stage tabs, and the round picker lists only the selected stage's rounds. Stage names come from the Search API. The Upcoming tab opens on the stage of the next match, the Results tab on the stage of the latest one. A stage with a single round, such as a final, shows no round picker. Single-stage leagues show neither.
The competition payload tags few matches with their stage, and a single matchday can hold matches from several groups at once, so the widget assigns each match itself: to the stage whose standings list both teams and whose dates cover the kick-off, otherwise to the nearest stage by date.
Not played. A postponed, cancelled, interrupted or abandoned match is listed on the Results tab, in its own round and day, with the reason in place of the score. It doesn't expand.
Live matches
A live match's row has a red accent, a pulsing dot and the match minute, with the half-time score under the current one once it is known. Expanding it shows the running match stats and goal timeline. While any match is live, the widget polls that match's own event endpoint every refreshInterval, not the competition endpoint. A match that has kicked off but is still marked scheduled in the competition payload is picked up too. Nothing is polled when no match is live.
Odds
With showOdds, each upcoming match row shows 1X2 odds from one operator — the same strip, settings and behaviour as CompetitorCard. A live match shows in-play prices; pre-match prices are never shown on a match in progress. One request covers every match in the round on screen. When the odds API has no complete market for a match, its row shows none. In a narrow container (under 600px) the odds move to a second line under the teams.
callbacks.onAddToBetslip receives { matchId, market, pick, odd } when a visitor clicks a price. The widget owns no betslip, so pass this to push the selection into yours — see Betslip. The operator's own link still opens either way.
TV broadcasts
The widget holds no broadcast data. Pass callbacks.onBroadcastClick and every upcoming match shows a TV icon next to its kick-off time. A click calls the handler with the match id, and the row doesn't expand. Without the callback, no icon renders. Which channel shows the match, and what the click opens (a modal, your TV guide, a channel page), is up to you.
<CompetitionStatistics
entityId="fb:c:3"
sdk={sdk}
language="en"
callbacks={{
onBroadcastClick: (matchId) => openTvSchedule(matchId),
}}
/>Link CTAs
interface CompetitionStatisticsLinkCTA {
onClick?: (id: string) => void;
url?: string | null;
target?: "_blank" | "_self" | null;
}url is a template: {id} is replaced with the clicked entity's URL-encoded id. {matchId}, {competitorId} and {athleteId} work as aliases on the matching CTA. With onClick the handler receives the id and the link's default navigation is prevented. Without either, the cards stay inert: no pointer cursor and no hover state.
<CompetitionStatistics
entityId="fb:c:3"
sdk={sdk}
language="en"
teamCTA={{ url: "/teams/{id}" }}
playerCTA={{ onClick: (athleteId) => router.push(`/players/${athleteId}`) }}
/>Labels
Every string falls back to the locale bundle (competitionStatistics.<key>), so override only what you want to reword. Keys carrying {{…}} are interpolated, so keep the placeholder. Count-dependent keys (e.g. matchesCount, unitWins) pick their plural form from the count.
<CompetitionStatistics
entityId="fb:c:3"
sdk={sdk}
language="en"
labels={{ leadersTitle: "Top performers", round: "Matchday {{round}}" }}
/>Examples
Full widget
import { CompetitionStatistics } from "fansunited-frontend-components";
<CompetitionStatistics entityId="fb:c:3" sdk={sdk} language="en" />;Fixtures only
<CompetitionStatistics
entityId="fb:c:3"
sdk={sdk}
language="en"
sections={["header", "matches"]}
/>Season statistics first
<CompetitionStatistics
entityId="fb:c:3"
sdk={sdk}
language="en"
sections={["season", "players", "matches"]}
/>With odds wired to the Betslip
import { CompetitionStatistics, betslipApi } from "fansunited-frontend-components";
<CompetitionStatistics
entityId="fb:c:3"
sdk={sdk}
language="en"
showOdds
odds={{ operators: ["betano-bg"], affiliateId: "aff-123" }}
callbacks={{
onAddToBetslip: (selection) =>
betslipApi.setSelection(`${selection.matchId}:${selection.market}:${selection.pick}`),
}}
/>;Updated about 9 hours ago
