diff --git a/dotcom-rendering/src/components/FootballMatchStat.stories.tsx b/dotcom-rendering/src/components/FootballMatchStat.stories.tsx
new file mode 100644
index 00000000000..339ecc24a57
--- /dev/null
+++ b/dotcom-rendering/src/components/FootballMatchStat.stories.tsx
@@ -0,0 +1,79 @@
+import { css } from '@emotion/react';
+import { space } from '@guardian/source/foundations';
+import type { Meta, StoryObj } from '@storybook/react-webpack5';
+import { palette } from '../palette';
+import { FootballMatchStat } from './FootballMatchStat';
+
+const meta = {
+ title: 'Components/Football Match Stat',
+ component: FootballMatchStat,
+ decorators: [
+ (Story) => (
+
+
+
+ ),
+ ],
+ parameters: {
+ viewport: {
+ defaultViewport: 'mobileMedium',
+ },
+ },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Default = {
+ args: {
+ label: 'Goal Attempts',
+ home: {
+ teamName: 'Manchester United',
+ teamColour: '#da020e',
+ value: 7,
+ },
+ away: {
+ teamName: 'Arsenal',
+ teamColour: '#023474',
+ value: 4,
+ },
+ },
+} satisfies Story;
+
+export const ShownAsPercentage = {
+ args: {
+ label: 'Possession',
+ home: {
+ teamName: 'West Ham',
+ teamColour: '#722642',
+ value: 39,
+ },
+ away: {
+ teamName: 'Newcastle',
+ teamColour: '#383838',
+ value: 61,
+ },
+ showPercentage: true,
+ },
+} satisfies Story;
+
+export const RaisedLabelOnDesktop = {
+ args: {
+ ...Default.args,
+ raiseLabelOnDesktop: true,
+ },
+} satisfies Story;
+
+export const LargeNumbersOnDesktop = {
+ args: {
+ ...Default.args,
+ largeNumbersOnDesktop: true,
+ },
+} satisfies Story;
diff --git a/dotcom-rendering/src/components/FootballMatchStat.tsx b/dotcom-rendering/src/components/FootballMatchStat.tsx
new file mode 100644
index 00000000000..c434a8a5ee3
--- /dev/null
+++ b/dotcom-rendering/src/components/FootballMatchStat.tsx
@@ -0,0 +1,165 @@
+import { css } from '@emotion/react';
+import {
+ from,
+ space,
+ textSansBold14,
+ textSansBold15,
+ textSansBold20,
+ textSansBold28,
+ visuallyHidden,
+} from '@guardian/source/foundations';
+import { palette } from '../palette';
+
+const containerCss = css`
+ position: relative;
+ padding: 5px 10px 10px;
+ border: 1px solid ${palette('--football-match-stat-border')};
+ border-radius: 6px;
+ &::before {
+ position: absolute;
+ content: '';
+ left: 50%;
+ bottom: 0;
+ width: 1px;
+ height: ${space[6]}px;
+ background-color: ${palette('--football-match-stat-border')};
+ }
+`;
+
+const headerCss = css`
+ display: grid;
+ grid-template-columns: auto 1fr auto;
+ grid-template-areas: 'home-stat label away-stat';
+`;
+
+const raiseLabelCss = css`
+ ${from.desktop} {
+ grid-template-areas:
+ 'label label label'
+ 'home-stat . away-stat';
+ }
+`;
+
+const labelCss = css`
+ ${textSansBold14};
+ grid-area: label;
+ justify-self: center;
+ color: ${palette('--football-match-stat-name')};
+ ${from.desktop} {
+ ${textSansBold15};
+ }
+`;
+
+const numberCss = css`
+ ${textSansBold20};
+ grid-area: home-stat;
+ color: var(--match-stat-team-colour);
+`;
+
+const largeNumberCss = css`
+ ${from.desktop} {
+ ${textSansBold28}
+ }
+`;
+
+const awayStatCss = css`
+ grid-area: away-stat;
+ justify-self: end;
+`;
+
+const chartCss = css`
+ position: relative;
+ display: flex;
+ gap: 10px;
+`;
+
+const barCss = css`
+ height: ${space[2]}px;
+ width: var(--match-stat-percentage);
+ background-color: var(--match-stat-team-colour);
+ border-radius: 8px;
+`;
+
+type MatchStatistic = {
+ teamName: string;
+ teamColour: string;
+ value: number;
+};
+
+type Props = {
+ label: string;
+ home: MatchStatistic;
+ away: MatchStatistic;
+ showPercentage?: boolean;
+ raiseLabelOnDesktop?: boolean;
+ largeNumbersOnDesktop?: boolean;
+};
+
+const formatValue = (value: number, showPercentage: boolean) =>
+ `${value}${showPercentage ? '%' : ''}`;
+
+export const FootballMatchStat = ({
+ label,
+ home,
+ away,
+ showPercentage = false,
+ raiseLabelOnDesktop = false,
+ largeNumbersOnDesktop = false,
+}: Props) => {
+ const homePercentage = (home.value / (home.value + away.value)) * 100;
+ const awayPercentage = (away.value / (home.value + away.value)) * 100;
+
+ return (
+
+
+ {label}
+
+
+ {home.teamName}
+
+ {formatValue(home.value, showPercentage)}
+
+
+
+ {away.teamName}
+
+ {formatValue(away.value, showPercentage)}
+
+
+
+
+ );
+};
diff --git a/dotcom-rendering/src/components/FootballMiniMatchStats.stories.tsx b/dotcom-rendering/src/components/FootballMiniMatchStats.stories.tsx
new file mode 100644
index 00000000000..7bfc0cbcbff
--- /dev/null
+++ b/dotcom-rendering/src/components/FootballMiniMatchStats.stories.tsx
@@ -0,0 +1,75 @@
+import { css } from '@emotion/react';
+import { breakpoints, from } from '@guardian/source/foundations';
+import type { Meta, StoryObj } from '@storybook/react-webpack5';
+import { palette } from '../palette';
+import { FootballMiniMatchStats as FootballMiniMatchStatsComponent } from './FootballMiniMatchStats';
+
+const gridCss = css`
+ background-color: ${palette('--football-live-blog-background')};
+ /**
+ * Extremely simplified live blog grid layout as we're only interested in
+ * the 240px wide left column added at the desktop breakpoint.
+ * dotcom-rendering/src/layouts/LiveLayout.tsx
+ */
+ ${from.desktop} {
+ display: grid;
+ grid-column-gap: 20px;
+ grid-template-columns: 240px 1fr;
+ }
+`;
+
+const containerCss = css`
+ padding: 10px;
+ ${from.desktop} {
+ padding-left: 20px;
+ padding-right: 0;
+ }
+`;
+
+const meta = {
+ title: 'Components/Football Mini Match Stats',
+ component: FootballMiniMatchStatsComponent,
+ decorators: [
+ (Story) => (
+
+ ),
+ ],
+ parameters: {
+ chromatic: {
+ viewports: [
+ breakpoints.mobileMedium,
+ breakpoints.tablet,
+ breakpoints.wide,
+ ],
+ },
+ },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const FootballMiniMatchStats = {
+ args: {
+ homeTeam: {
+ name: 'Manchester United',
+ colour: '#da020e',
+ },
+ awayTeam: {
+ name: 'Arsenal',
+ colour: '#023474',
+ },
+ stats: [
+ {
+ label: 'Possession',
+ homeValue: 39,
+ awayValue: 61,
+ showPercentage: true,
+ },
+ { label: 'Goal Attempts', homeValue: 7, awayValue: 4 },
+ ],
+ },
+} satisfies Story;
diff --git a/dotcom-rendering/src/components/FootballMiniMatchStats.tsx b/dotcom-rendering/src/components/FootballMiniMatchStats.tsx
new file mode 100644
index 00000000000..0c335fcb1cd
--- /dev/null
+++ b/dotcom-rendering/src/components/FootballMiniMatchStats.tsx
@@ -0,0 +1,91 @@
+import { css } from '@emotion/react';
+import { from } from '@guardian/source/foundations';
+import {
+ LinkButton,
+ SvgArrowRightStraight,
+} from '@guardian/source/react-components';
+import { palette } from '../palette';
+import { FootballMatchStat } from './FootballMatchStat';
+
+const containerCss = css`
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+`;
+
+const buttonTextCss = css`
+ ${from.desktop} {
+ display: none;
+ }
+`;
+
+const buttonTextShortCss = css`
+ display: none;
+ ${from.desktop} {
+ display: inline;
+ }
+`;
+
+type FootballTeam = {
+ name: string;
+ colour: string;
+};
+
+type MatchStatistic = {
+ label: string;
+ homeValue: number;
+ awayValue: number;
+ showPercentage?: boolean;
+};
+
+type Props = {
+ homeTeam: FootballTeam;
+ awayTeam: FootballTeam;
+ stats: MatchStatistic[];
+};
+
+export const FootballMiniMatchStats = ({
+ homeTeam,
+ awayTeam,
+ stats,
+}: Props) => {
+ return (
+
+ {stats.map((stat) => (
+
+ ))}
+ }
+ iconSide="right"
+ theme={{
+ backgroundPrimary: palette(
+ '--football-match-stat-button-background',
+ ),
+ backgroundPrimaryHover: palette(
+ '--football-match-stat-button-background-hover',
+ ),
+ }}
+ >
+ More stats, line-ups and tables
+ Stats and line ups
+
+
+ );
+};
diff --git a/dotcom-rendering/src/paletteDeclarations.ts b/dotcom-rendering/src/paletteDeclarations.ts
index 061f0926be6..bd4bdc42329 100644
--- a/dotcom-rendering/src/paletteDeclarations.ts
+++ b/dotcom-rendering/src/paletteDeclarations.ts
@@ -7043,6 +7043,10 @@ const paletteColours = {
light: () => '#3DB540',
dark: () => '#3DB540',
},
+ '--football-live-blog-background': {
+ light: () => sourcePalette.neutral[97],
+ dark: () => sourcePalette.neutral[10],
+ },
'--football-match-hover': {
light: () => sourcePalette.neutral[93],
dark: () => sourcePalette.neutral[38],
@@ -7055,6 +7059,22 @@ const paletteColours = {
light: () => sourcePalette.sport[500],
dark: () => sourcePalette.sport[500],
},
+ '--football-match-stat-border': {
+ light: () => sourcePalette.neutral[86],
+ dark: () => sourcePalette.neutral[38],
+ },
+ '--football-match-stat-button-background': {
+ light: () => sourcePalette.sport[400],
+ dark: () => sourcePalette.sport[500],
+ },
+ '--football-match-stat-button-background-hover': {
+ light: () => '#00679E', // replace with Source's `calculateHoverColour` when available
+ dark: () => '#00A1E6',
+ },
+ '--football-match-stat-name': {
+ light: () => sourcePalette.neutral[7],
+ dark: () => sourcePalette.neutral[86],
+ },
'--football-score-border': {
light: () => sourcePalette.neutral[7],
dark: () => sourcePalette.neutral[7],