diff --git a/dotcom-rendering/fixtures/generated/match-report.ts b/dotcom-rendering/fixtures/generated/match-report.ts
index abf5e631e9a..40cd0a4d3c7 100644
--- a/dotcom-rendering/fixtures/generated/match-report.ts
+++ b/dotcom-rendering/fixtures/generated/match-report.ts
@@ -274,7 +274,7 @@ export const matchReport: MatchReportType = {
shotsOff: 6,
corners: 10,
fouls: 4,
- colours: '#ffffff',
+ colours: '#01009a',
crest: 'https://sport.guim.co.uk/football/crests/120/7699.png',
},
awayTeam: {
diff --git a/dotcom-rendering/src/components/FootballMatchStat.tsx b/dotcom-rendering/src/components/FootballMatchStat.tsx
index c434a8a5ee3..5f4d350b965 100644
--- a/dotcom-rendering/src/components/FootballMatchStat.tsx
+++ b/dotcom-rendering/src/components/FootballMatchStat.tsx
@@ -44,7 +44,7 @@ const labelCss = css`
${textSansBold14};
grid-area: label;
justify-self: center;
- color: ${palette('--football-match-stat-name')};
+ color: ${palette('--football-match-stat-text')};
${from.desktop} {
${textSansBold15};
}
diff --git a/dotcom-rendering/src/components/Lineups.stories.tsx b/dotcom-rendering/src/components/Lineups.stories.tsx
new file mode 100644
index 00000000000..27575cfd3b9
--- /dev/null
+++ b/dotcom-rendering/src/components/Lineups.stories.tsx
@@ -0,0 +1,55 @@
+import { css } from '@emotion/react';
+import type { Meta, StoryObj } from '@storybook/react-webpack5';
+import { allModes } from '../../.storybook/modes';
+import { matchReport } from '../../fixtures/generated/match-report';
+import { grid } from '../grid';
+import { palette } from '../palette';
+import { type TeamType } from '../types/sport';
+import { Lineups as LineupsComponent } from './Lineups';
+
+const homeTeam: TeamType = matchReport.homeTeam;
+
+const awayTeam: TeamType = matchReport.awayTeam;
+
+const meta = {
+ title: 'Components/Lineups',
+ component: LineupsComponent,
+ parameters: {
+ chromatic: {
+ modes: {
+ 'light leftCol': allModes['light leftCol'],
+ },
+ },
+ },
+ decorators: [
+ (Story) => (
+
+ ),
+ ],
+} satisfies Meta;
+
+export default meta;
+
+type Story = StoryObj;
+
+export const LineupsStory = {
+ args: {
+ home: homeTeam,
+ away: awayTeam,
+ },
+} satisfies Story;
diff --git a/dotcom-rendering/src/components/Lineups.tsx b/dotcom-rendering/src/components/Lineups.tsx
new file mode 100644
index 00000000000..83a8be86ee2
--- /dev/null
+++ b/dotcom-rendering/src/components/Lineups.tsx
@@ -0,0 +1,224 @@
+import { css } from '@emotion/react';
+import {
+ from,
+ palette as sourcePalette,
+ space,
+ textSans14,
+ textSans15,
+ textSansBold12,
+ textSansBold14,
+} from '@guardian/source/foundations';
+import type { FootballTeam } from '../footballMatch';
+import { palette } from '../palette';
+import Union from '../static/icons/Union.svg';
+import type { EventType } from '../types/sport';
+
+type Props = {
+ home: FootballTeam;
+ away: FootballTeam;
+};
+
+const lineupSectionId = 'lineups';
+const substitutesSectionId = 'substitutes';
+
+export const Lineups = ({ home, away }: Props) => {
+ return (
+
+ );
+};
+
+const Event = ({
+ type,
+ time,
+}: {
+ type: 'substitution' | 'dismissal' | 'booking';
+ time: string;
+}) => {
+ switch (type) {
+ case 'dismissal':
+ return (
+
+ );
+ case 'booking':
+ return (
+
+ );
+ case 'substitution':
+ return (
+
+
+ {time}
+
+ );
+ }
+};
+
+const Title = ({ text, id }: { text: string; id: string }) => (
+
+ {text}
+
+);
+
+const PlayerList = ({
+ team,
+ isSubstitute,
+ isHome,
+}: {
+ team: FootballTeam;
+ isSubstitute: boolean;
+ isHome: boolean;
+}) => {
+ return (
+
+ {team.players
+ .filter((player) => player.substitute === isSubstitute)
+ .map((player) => (
+ -
+
+ {player.shirtNumber}
+
+
+ {player.name.charAt(0).toUpperCase()}.{' '}
+ {player.lastName}
+
+ {player.events.map((event: EventType) => (
+
+ ))}
+
+ ))}
+
+ );
+};
+
+const sectionStyles = css`
+ border: 1px solid ${palette('--football-match-stat-border')};
+ margin: ${space[2]}px;
+ border-radius: 6px;
+
+ padding-top: 6px;
+
+ background-color: ${palette('--football-match-info-background')};
+`;
+
+const playerListSectionGridStyles = css`
+ display: grid;
+ grid-template-columns: [home-start] 1fr [home-end away-start] 1fr [away-end];
+ column-gap: 20px;
+
+ padding: 0px 10px 10px 10px;
+`;
+
+const homeStyles = css`
+ grid-column: home-start / home-end;
+`;
+
+const awayStyles = css`
+ grid-column: away-start / away-end;
+ position: relative;
+ &::before {
+ content: '';
+ position: absolute;
+ left: -10px;
+ top: 0;
+ bottom: 0;
+ width: 1px;
+ background-color: ${palette('--football-match-stat-border')};
+ }
+`;
+
+const shirtNumber = (color: string) => css`
+ display: inline-block;
+ width: ${space[5]}px;
+ ${textSansBold14}
+ color: ${color};
+`;
+
+const listItem = css`
+ padding-top: ${space[2]}px;
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: ${space[1]}px;
+
+ &:last-child {
+ padding-bottom: ${space[0]}px;
+ }
+`;
+
+const playerName = css`
+ ${textSans14}
+ ${from.tablet} {
+ ${textSans15}
+ }
+ color: ${palette('--football-match-stat-text')};
+`;
+
+const BackgroundRed = sourcePalette.news[400];
+
+const BackgroundYellow = sourcePalette.brandAlt[300];
+
+const rectangle = (color: string) => css`
+ display: inline-block;
+ width: ${space[3]}px;
+ height: ${space[4]}px;
+ border-radius: ${space[0]}px;
+ margin-left: ${space[1]}px;
+ background-color: ${color};
+`;
+
+const substitute = css`
+ ${textSansBold12}
+ border: 1px solid rgba(18, 18, 18, 0.20);
+ border-radius: ${space[8]}px;
+ padding: 0.5px ${space[1]}px 1.5px ${space[1]}px;
+ display: flex;
+ align-items: center;
+ color: ${palette('--football-match-stat-text')};
+ opacity: 0.6;
+ gap: ${space[0]}px;
+
+ svg {
+ fill: ${palette('--football-match-substitution-icon')};
+ }
+`;
diff --git a/dotcom-rendering/src/paletteDeclarations.ts b/dotcom-rendering/src/paletteDeclarations.ts
index bd4bdc42329..c4211185d75 100644
--- a/dotcom-rendering/src/paletteDeclarations.ts
+++ b/dotcom-rendering/src/paletteDeclarations.ts
@@ -7051,6 +7051,10 @@ const paletteColours = {
light: () => sourcePalette.neutral[93],
dark: () => sourcePalette.neutral[38],
},
+ '--football-match-info-background': {
+ light: () => sourcePalette.neutral[100],
+ dark: () => sourcePalette.neutral[10],
+ },
'--football-match-list-error': {
light: () => sourcePalette.error[400],
dark: () => sourcePalette.error[500],
@@ -7071,10 +7075,14 @@ const paletteColours = {
light: () => '#00679E', // replace with Source's `calculateHoverColour` when available
dark: () => '#00A1E6',
},
- '--football-match-stat-name': {
+ '--football-match-stat-text': {
light: () => sourcePalette.neutral[7],
dark: () => sourcePalette.neutral[86],
},
+ '--football-match-substitution-icon': {
+ light: () => sourcePalette.neutral[46],
+ dark: () => sourcePalette.neutral[60],
+ },
'--football-score-border': {
light: () => sourcePalette.neutral[7],
dark: () => sourcePalette.neutral[7],
diff --git a/dotcom-rendering/src/static/icons/Union.svg b/dotcom-rendering/src/static/icons/Union.svg
new file mode 100644
index 00000000000..39744f7af37
--- /dev/null
+++ b/dotcom-rendering/src/static/icons/Union.svg
@@ -0,0 +1,3 @@
+