diff --git a/dotcom-rendering/src/components/Masthead/Masthead.tsx b/dotcom-rendering/src/components/Masthead/Masthead.tsx
index 922d38d46bd..71ab5ed1e1f 100644
--- a/dotcom-rendering/src/components/Masthead/Masthead.tsx
+++ b/dotcom-rendering/src/components/Masthead/Masthead.tsx
@@ -2,7 +2,7 @@ import type { ReactElement } from 'react';
import type { EditionId } from '../../lib/edition';
import type { NavType } from '../../model/extract-nav';
import { palette as themePalette } from '../../palette';
-import type { CustomSubnav } from '../../types/customSubnav';
+import type { CustomSubnavForPage } from '../../types/customSubnav';
import { Island } from '../Island';
import { Section } from '../Section';
import { Titlepiece } from '../Titlepiece.island';
@@ -18,7 +18,7 @@ type Props = {
discussionApiUrl: string;
idApiUrl: string;
showSubNav?: boolean;
- customSubnav?: CustomSubnav;
+ customSubnav?: CustomSubnavForPage;
/** The slim nav is a slimmed down version of the Titlepiece usually used for Immersive articles */
showSlimNav?: boolean;
hasPageSkin?: boolean;
diff --git a/dotcom-rendering/src/components/Masthead/Titlepiece/CustomSubNav.stories.tsx b/dotcom-rendering/src/components/Masthead/Titlepiece/CustomSubNav.stories.tsx
new file mode 100644
index 00000000000..8e8800f4f12
--- /dev/null
+++ b/dotcom-rendering/src/components/Masthead/Titlepiece/CustomSubNav.stories.tsx
@@ -0,0 +1,78 @@
+import { css } from '@emotion/react';
+import { palette as sourcePalette, space } from '@guardian/source/foundations';
+import type { Meta, StoryObj } from '@storybook/react-webpack5';
+import {
+ ArticleDesign,
+ ArticleDisplay,
+ Pillar,
+} from '../../../lib/articleFormat';
+import type { CustomSubnav } from '../../../types/customSubnav';
+import { CustomSubNav } from './CustomSubNav';
+
+const customSubNav: CustomSubnav = {
+ id: 'us-politics',
+ header: {
+ headerText: 'US politics',
+ copy: '',
+ dotcomPath: '/us-news/us-politics',
+ },
+ format: 'large',
+ links: [
+ {
+ linkText: 'Trump administration',
+ dotcomPath: '/us-news/trump-administration',
+ },
+ { linkText: 'US Congress', dotcomPath: '/us-news/us-congress' },
+ { linkText: 'US supreme court', dotcomPath: '/law/us-supreme-court' },
+ {
+ linkText: 'US elections 2024',
+ dotcomPath: '/us-news/us-elections-2024',
+ },
+ { linkText: 'US immigration', dotcomPath: '/us-news/usimmigration' },
+ ],
+ pages: [],
+};
+
+const meta = {
+ component: CustomSubNav,
+ title: 'Components/Masthead/Titlepiece/CustomSubNav',
+ decorators: [
+ (Story) => (
+
+
+
+ ),
+ ],
+ render: (args) => ,
+ args: {
+ customSubNav,
+ currentNavLink: 'US Congress',
+ hasPageSkin: false,
+ },
+} satisfies Meta;
+
+export default meta;
+
+type Story = StoryObj;
+
+export const Front = {
+ args: { renderingPage: 'front' },
+} satisfies Story;
+
+export const Article = {
+ args: { renderingPage: 'article' },
+ parameters: {
+ formats: [
+ {
+ design: ArticleDesign.Standard,
+ display: ArticleDisplay.Standard,
+ theme: Pillar.News,
+ },
+ ],
+ },
+} satisfies Story;
diff --git a/dotcom-rendering/src/components/Masthead/Titlepiece/CustomSubNav.tsx b/dotcom-rendering/src/components/Masthead/Titlepiece/CustomSubNav.tsx
index 6c1432275e7..06a7001b65b 100644
--- a/dotcom-rendering/src/components/Masthead/Titlepiece/CustomSubNav.tsx
+++ b/dotcom-rendering/src/components/Masthead/Titlepiece/CustomSubNav.tsx
@@ -4,19 +4,22 @@
*/
import { css } from '@emotion/react';
import {
+ breakpoints,
from,
headlineBold28,
space,
textSans14,
textSansBold14,
} from '@guardian/source/foundations';
+import { grid } from '../../../grid';
import { nestedOphanComponents } from '../../../lib/ophan-helpers';
import { palette as themePalette } from '../../../palette';
-import type { CustomSubnav } from '../../../types/customSubnav';
+import type { CustomSubnav, RenderingPage } from '../../../types/customSubnav';
type Props = {
customSubNav: CustomSubnav;
currentNavLink: string;
+ renderingPage: RenderingPage;
hasPageSkin?: boolean;
};
@@ -43,6 +46,132 @@ const subNavStylesFromLeftCol = css`
}
`;
+/** On articles the header and links sit on a single aligned row. */
+const articleContainerStyles = css`
+ display: flex;
+ align-items: stretch;
+ min-height: 36px;
+
+ ${from.mobileMedium} {
+ min-height: 40px;
+ }
+ ${from.tablet} {
+ min-height: 42px;
+ }
+ ${from.leftCol} {
+ min-height: 44px;
+ }
+`;
+
+/**
+ * On articles the links row sits inline next to the header. The stacked top margins are
+ * removed so the header divider spans the full component height set by the container.
+ */
+const articleSubNavStyles = css`
+ margin-top: 0;
+ align-items: center;
+
+ ${from.mobileMedium} {
+ margin-top: 0;
+ }
+ ${from.leftCol} {
+ margin-top: 0;
+ }
+`;
+
+const articleHeaderStyles = css`
+ ${textSansBold14}
+ color: ${themePalette('--custom-subnav-header-text')};
+ display: flex;
+ align-items: center;
+ white-space: nowrap;
+ padding-right: ${space[2]}px;
+ margin-right: ${space[2]}px;
+ border-right: 1px solid ${themePalette('--masthead-nav-lines')};
+`;
+
+/** Served from `src/static`; temporary test assets until real images arrive in the payload. */
+const headerImageBasePath = '/static/frontend/customsubnav';
+
+/**
+ * On fronts, when an image is present the subnav mirrors DirectoryPageNav: a
+ * fixed-width, centred grid (blue) with the image spanning the full width on the
+ * top row and the links beneath it. The surrounding row is white (set by the
+ * Titlepiece wrapper), so only this padded container shows blue.
+ */
+const imageNavStyles = css`
+ ${grid.paddedContainer}
+ position: relative;
+ background-color: ${themePalette('--masthead-nav-background')};
+`;
+
+const imageWrapperStyles = css`
+ ${grid.column.all}
+ grid-row: 1;
+ position: relative;
+ display: block;
+ border-bottom: 1px solid ${themePalette('--masthead-nav-lines')};
+`;
+
+const headerImageStyles = css`
+ display: block;
+ width: 100%;
+ height: auto;
+`;
+
+const imageHeaderTextStyles = css`
+ ${headlineBold28}
+ position: absolute;
+ left: ${space[3]}px;
+ bottom: ${space[1]}px;
+ color: ${themePalette('--masthead-nav-link-text')};
+
+ ${from.mobileLandscape} {
+ left: ${space[5]}px;
+ }
+`;
+
+const imageListStyles = css`
+ ${grid.column.all}
+ grid-row: 2;
+ ${textSans14}
+ display: flex;
+ align-items: center;
+ column-gap: ${space[2]}px;
+ min-height: 28px;
+ padding: 0 ${space[3]}px;
+
+ ${from.mobileLandscape} {
+ padding: 0 ${space[5]}px;
+ }
+ ${from.tablet} {
+ min-height: 30px;
+ }
+`;
+
+/** Temporary breakpoint-specific header images; swap for payload images later. */
+const HeaderImage = () => (
+
+
+
+
+
+
+);
+
/** Sets horizontal scrolling behaviour and removes the scrollbar */
const scrollableSubNavStyles = css`
overflow-x: scroll;
@@ -75,56 +204,104 @@ const selectedLink = css`
`;
/**
- * Renders a custom subnav (header + links, images and further data to follow) targeted at a front.
+ * Renders a custom subnav (header + links, images and further data to follow) assigned to fronts or articles.
*/
export const CustomSubNav = ({
customSubNav,
currentNavLink,
+ renderingPage,
hasPageSkin,
}: Props) => {
+ const isArticle = renderingPage === 'article';
+ const hasHeaderImage = !isArticle && (customSubNav.images?.length ?? 0) > 0;
+
+ const linkItems = customSubNav.links.map(({ linkText, dotcomPath }) => (
+
+
+ {linkText === currentNavLink ? (
+ {linkText}
+ ) : (
+ linkText
+ )}
+
+
+ ));
+
+ if (hasHeaderImage) {
+ return (
+
+
+
+
+ {customSubNav.header.headerText}
+
+
+
+
+ );
+ }
+
return (
- {customSubNav.header.headerText}
+ {isArticle ? (
+
+ {customSubNav.header.headerText}
+
+ ) : (
+ customSubNav.header.headerText
+ )}
);
diff --git a/dotcom-rendering/src/components/Titlepiece.island.tsx b/dotcom-rendering/src/components/Titlepiece.island.tsx
index ed6164cba2c..529ebf6bc21 100644
--- a/dotcom-rendering/src/components/Titlepiece.island.tsx
+++ b/dotcom-rendering/src/components/Titlepiece.island.tsx
@@ -2,6 +2,7 @@ import { css, Global } from '@emotion/react';
import {
from,
headlineBold14,
+ palette as sourcePalette,
space,
until,
visuallyHidden,
@@ -11,7 +12,7 @@ import { nestedOphanComponents } from '../lib/ophan-helpers';
import { useEditionSwitcherBanner } from '../lib/useEditionSwitcherBanner';
import type { NavType } from '../model/extract-nav';
import { palette as themePalette } from '../palette';
-import type { CustomSubnav } from '../types/customSubnav';
+import type { CustomSubnavForPage } from '../types/customSubnav';
import {
expandedMenuRootId,
navInputCheckboxId,
@@ -32,7 +33,7 @@ interface Props {
nav: NavType;
editionId: EditionId;
showSubNav?: boolean;
- customSubnav?: CustomSubnav;
+ customSubnav?: CustomSubnavForPage;
showSlimNav?: boolean;
hasPageSkin?: boolean;
pageId?: string;
@@ -316,6 +317,14 @@ const fadeStyles = css`
${themePalette('--masthead-nav-background')} 100%
);
`;
+
+/** For the image custom subnav the row is white and full-bleed; CustomSubNav
+ * centres its own blue padded container on top (mirrors DirectoryPageNav). */
+const customSubnavImageWrapper = css`
+ grid-column: viewport-start / viewport-end;
+ grid-row: 3;
+ background-color: ${sourcePalette.neutral[100]};
+`;
export const Titlepiece = ({
nav,
editionId,
@@ -327,6 +336,10 @@ export const Titlepiece = ({
}: Props) => {
const { showBanner } = useEditionSwitcherBanner(pageId, editionId);
+ const hasCustomSubnavImage =
+ customSubnav?.renderingPage === 'front' &&
+ (customSubnav.data.images?.length ?? 0) > 0;
+
return (
diff --git a/dotcom-rendering/src/frontend/schemas/feArticle.json b/dotcom-rendering/src/frontend/schemas/feArticle.json
index c4acc49dbf3..0fe237e54b9 100644
--- a/dotcom-rendering/src/frontend/schemas/feArticle.json
+++ b/dotcom-rendering/src/frontend/schemas/feArticle.json
@@ -6625,7 +6625,6 @@
]
},
"CustomSubnavLink": {
- "description": "A custom subnav that can appear on assigned pages (e.g. fronts, articles). It arrives inside the `nav` object of the request payload.",
"type": "object",
"properties": {
"linkText": {
diff --git a/dotcom-rendering/src/frontend/schemas/feFootballMatchInfoPage.json b/dotcom-rendering/src/frontend/schemas/feFootballMatchInfoPage.json
index 6e722c8c2de..c858195d643 100644
--- a/dotcom-rendering/src/frontend/schemas/feFootballMatchInfoPage.json
+++ b/dotcom-rendering/src/frontend/schemas/feFootballMatchInfoPage.json
@@ -437,7 +437,6 @@
]
},
"CustomSubnavLink": {
- "description": "A custom subnav that can appear on assigned pages (e.g. fronts, articles). It arrives inside the `nav` object of the request payload.",
"type": "object",
"properties": {
"linkText": {
diff --git a/dotcom-rendering/src/frontend/schemas/feFootballMatchListPage.json b/dotcom-rendering/src/frontend/schemas/feFootballMatchListPage.json
index 467b5945d6f..fefacafb810 100644
--- a/dotcom-rendering/src/frontend/schemas/feFootballMatchListPage.json
+++ b/dotcom-rendering/src/frontend/schemas/feFootballMatchListPage.json
@@ -319,7 +319,6 @@
]
},
"CustomSubnavLink": {
- "description": "A custom subnav that can appear on assigned pages (e.g. fronts, articles). It arrives inside the `nav` object of the request payload.",
"type": "object",
"properties": {
"linkText": {
diff --git a/dotcom-rendering/src/frontend/schemas/feFootballTablesPage.json b/dotcom-rendering/src/frontend/schemas/feFootballTablesPage.json
index b67921976dc..f5e90c8a41f 100644
--- a/dotcom-rendering/src/frontend/schemas/feFootballTablesPage.json
+++ b/dotcom-rendering/src/frontend/schemas/feFootballTablesPage.json
@@ -312,7 +312,6 @@
]
},
"CustomSubnavLink": {
- "description": "A custom subnav that can appear on assigned pages (e.g. fronts, articles). It arrives inside the `nav` object of the request payload.",
"type": "object",
"properties": {
"linkText": {
diff --git a/dotcom-rendering/src/frontend/schemas/feFront.json b/dotcom-rendering/src/frontend/schemas/feFront.json
index b850763db67..f73e02f5604 100644
--- a/dotcom-rendering/src/frontend/schemas/feFront.json
+++ b/dotcom-rendering/src/frontend/schemas/feFront.json
@@ -3645,7 +3645,6 @@
]
},
"CustomSubnavLink": {
- "description": "A custom subnav that can appear on assigned pages (e.g. fronts, articles). It arrives inside the `nav` object of the request payload.",
"type": "object",
"properties": {
"linkText": {
diff --git a/dotcom-rendering/src/frontend/schemas/feTagPage.json b/dotcom-rendering/src/frontend/schemas/feTagPage.json
index 192ac9e2a8b..191d0b5be5a 100644
--- a/dotcom-rendering/src/frontend/schemas/feTagPage.json
+++ b/dotcom-rendering/src/frontend/schemas/feTagPage.json
@@ -1703,7 +1703,6 @@
]
},
"CustomSubnavLink": {
- "description": "A custom subnav that can appear on assigned pages (e.g. fronts, articles). It arrives inside the `nav` object of the request payload.",
"type": "object",
"properties": {
"linkText": {
diff --git a/dotcom-rendering/src/layouts/CommentLayout.tsx b/dotcom-rendering/src/layouts/CommentLayout.tsx
index 446ceb3daa3..6a6b6ca3a33 100644
--- a/dotcom-rendering/src/layouts/CommentLayout.tsx
+++ b/dotcom-rendering/src/layouts/CommentLayout.tsx
@@ -333,7 +333,13 @@ export const CommentLayout = (props: WebProps | AppsProps) => {
discussionApiUrl={article.config.discussionApiUrl}
idApiUrl={article.config.idApiUrl}
contributionsServiceUrl={contributionsServiceUrl}
- showSubNav={!isWorldCup2026}
+ showSubNav={!isWorldCup2026 && !props.NAV.customSubNav}
+ customSubnav={
+ props.NAV.customSubNav && {
+ data: props.NAV.customSubNav,
+ renderingPage: 'article',
+ }
+ }
showSlimNav={false}
hasPageSkin={false}
hasPageSkinContentSelfConstrain={false}
diff --git a/dotcom-rendering/src/layouts/FrontLayout.tsx b/dotcom-rendering/src/layouts/FrontLayout.tsx
index e223724074c..78a5ee7d428 100644
--- a/dotcom-rendering/src/layouts/FrontLayout.tsx
+++ b/dotcom-rendering/src/layouts/FrontLayout.tsx
@@ -240,7 +240,12 @@ export const FrontLayout = ({ front, NAV }: Props) => {
contributionsServiceUrl={contributionsServiceUrl}
idApiUrl={front.config.idApiUrl}
showSubNav={showStandardSubNav}
- customSubnav={NAV.customSubNav}
+ customSubnav={
+ NAV.customSubNav && {
+ data: NAV.customSubNav,
+ renderingPage: 'front',
+ }
+ }
showSlimNav={false}
hasPageSkin={hasPageSkin}
hasPageSkinContentSelfConstrain={true}
diff --git a/dotcom-rendering/src/layouts/FullPageInteractiveLayout.tsx b/dotcom-rendering/src/layouts/FullPageInteractiveLayout.tsx
index 0bb9c91a48c..6506e0d55e7 100644
--- a/dotcom-rendering/src/layouts/FullPageInteractiveLayout.tsx
+++ b/dotcom-rendering/src/layouts/FullPageInteractiveLayout.tsx
@@ -175,6 +175,12 @@ const NavHeader = ({ article, NAV, renderAds }: HeaderProps) => {
idApiUrl={article.config.idApiUrl}
contributionsServiceUrl={article.contributionsServiceUrl}
showSubNav={false}
+ customSubnav={
+ NAV.customSubNav && {
+ data: NAV.customSubNav,
+ renderingPage: 'article',
+ }
+ }
showSlimNav={true}
hasPageSkin={false}
hasPageSkinContentSelfConstrain={false}
diff --git a/dotcom-rendering/src/layouts/GalleryLayout.tsx b/dotcom-rendering/src/layouts/GalleryLayout.tsx
index c550dd30133..b49cfec13bb 100644
--- a/dotcom-rendering/src/layouts/GalleryLayout.tsx
+++ b/dotcom-rendering/src/layouts/GalleryLayout.tsx
@@ -416,6 +416,12 @@ const BannerAndMasthead = (props: {
idApiUrl={props.config.idApiUrl}
contributionsServiceUrl={props.contributionsServiceUrl}
showSubNav={false}
+ customSubnav={
+ props.nav.customSubNav && {
+ data: props.nav.customSubNav,
+ renderingPage: 'article',
+ }
+ }
showSlimNav={props.showSlimNav ?? true}
hasPageSkin={false}
hasPageSkinContentSelfConstrain={false}
diff --git a/dotcom-rendering/src/layouts/InteractiveLayout.tsx b/dotcom-rendering/src/layouts/InteractiveLayout.tsx
index 80e2f30611e..07df0b43576 100644
--- a/dotcom-rendering/src/layouts/InteractiveLayout.tsx
+++ b/dotcom-rendering/src/layouts/InteractiveLayout.tsx
@@ -284,6 +284,12 @@ export const InteractiveLayout = (props: WebProps | AppsProps) => {
contributionsServiceUrl={contributionsServiceUrl}
showSlimNav={true}
showSubNav={false}
+ customSubnav={
+ props.NAV.customSubNav && {
+ data: props.NAV.customSubNav,
+ renderingPage: 'article',
+ }
+ }
hasPageSkin={false}
hasPageSkinContentSelfConstrain={false}
pageId={article.pageId}
diff --git a/dotcom-rendering/src/layouts/LiveLayout.tsx b/dotcom-rendering/src/layouts/LiveLayout.tsx
index 5c9d2dcd57b..c768bec5af3 100644
--- a/dotcom-rendering/src/layouts/LiveLayout.tsx
+++ b/dotcom-rendering/src/layouts/LiveLayout.tsx
@@ -338,7 +338,13 @@ export const LiveLayout = (props: WebProps | AppsProps) => {
discussionApiUrl={article.config.discussionApiUrl}
idApiUrl={article.config.idApiUrl}
contributionsServiceUrl={contributionsServiceUrl}
- showSubNav={!isWorldCup2026}
+ showSubNav={!isWorldCup2026 && !props.NAV.customSubNav}
+ customSubnav={
+ props.NAV.customSubNav && {
+ data: props.NAV.customSubNav,
+ renderingPage: 'article',
+ }
+ }
showSlimNav={false}
hasPageSkin={false}
hasPageSkinContentSelfConstrain={false}
diff --git a/dotcom-rendering/src/layouts/PictureLayout.tsx b/dotcom-rendering/src/layouts/PictureLayout.tsx
index f4e3f55728c..99657235c63 100644
--- a/dotcom-rendering/src/layouts/PictureLayout.tsx
+++ b/dotcom-rendering/src/layouts/PictureLayout.tsx
@@ -309,7 +309,13 @@ export const PictureLayout = (props: WebProps | AppsProps) => {
discussionApiUrl={article.config.discussionApiUrl}
idApiUrl={article.config.idApiUrl}
contributionsServiceUrl={contributionsServiceUrl}
- showSubNav={!isWorldCup2026}
+ showSubNav={!isWorldCup2026 && !props.NAV.customSubNav}
+ customSubnav={
+ props.NAV.customSubNav && {
+ data: props.NAV.customSubNav,
+ renderingPage: 'article',
+ }
+ }
showSlimNav={false}
hasPageSkin={false}
hasPageSkinContentSelfConstrain={false}
diff --git a/dotcom-rendering/src/layouts/StandardLayout.tsx b/dotcom-rendering/src/layouts/StandardLayout.tsx
index cd5abdf5f3d..8be67fb950d 100644
--- a/dotcom-rendering/src/layouts/StandardLayout.tsx
+++ b/dotcom-rendering/src/layouts/StandardLayout.tsx
@@ -119,7 +119,17 @@ export const StandardLayout = (props: WebProps | AppProps) => {
discussionApiUrl={article.config.discussionApiUrl}
idApiUrl={article.config.idApiUrl}
contributionsServiceUrl={contributionsServiceUrl}
- showSubNav={!isLabs && !isWorldCup2026}
+ showSubNav={
+ !isLabs &&
+ !isWorldCup2026 &&
+ !props.NAV.customSubNav
+ }
+ customSubnav={
+ props.NAV.customSubNav && {
+ data: props.NAV.customSubNav,
+ renderingPage: 'article',
+ }
+ }
showSlimNav={
format.display === ArticleDisplay.Immersive
}
diff --git a/dotcom-rendering/src/model/newsletter-page-schema.json b/dotcom-rendering/src/model/newsletter-page-schema.json
index 2963f083b0e..a01a2a4d791 100644
--- a/dotcom-rendering/src/model/newsletter-page-schema.json
+++ b/dotcom-rendering/src/model/newsletter-page-schema.json
@@ -456,7 +456,6 @@
]
},
"CustomSubnavLink": {
- "description": "A custom subnav that can appear on assigned pages (e.g. fronts, articles). It arrives inside the `nav` object of the request payload.",
"type": "object",
"properties": {
"linkText": {
diff --git a/dotcom-rendering/src/paletteDeclarations.ts b/dotcom-rendering/src/paletteDeclarations.ts
index dd7d58bceee..3d66a6c4cd3 100644
--- a/dotcom-rendering/src/paletteDeclarations.ts
+++ b/dotcom-rendering/src/paletteDeclarations.ts
@@ -6572,6 +6572,39 @@ const crosswordCluesHeaderBorderBottom: PaletteFunction = () =>
const crosswordTextLight: PaletteFunction = () => sourcePalette.neutral[7];
const crosswordTextDark: PaletteFunction = () => sourcePalette.neutral[86];
+const customSubnavHeaderTextLight: PaletteFunction = ({ theme }) => {
+ switch (theme) {
+ case Pillar.News:
+ case Pillar.Opinion:
+ case Pillar.Sport:
+ case Pillar.Culture:
+ case Pillar.Lifestyle:
+ return pillarPalette(theme, 500);
+ case ArticleSpecial.Labs:
+ return sourcePalette.labs[500];
+ case ArticleSpecial.SpecialReport:
+ return sourcePalette.specialReport[500];
+ case ArticleSpecial.SpecialReportAlt:
+ return sourcePalette.specialReportAlt[300];
+ }
+};
+const customSubnavHeaderTextDark: PaletteFunction = ({ theme }) => {
+ switch (theme) {
+ case Pillar.News:
+ case Pillar.Opinion:
+ case Pillar.Sport:
+ case Pillar.Culture:
+ case Pillar.Lifestyle:
+ return pillarPalette(theme, 500);
+ case ArticleSpecial.Labs:
+ return sourcePalette.labs[400];
+ case ArticleSpecial.SpecialReport:
+ return sourcePalette.specialReport[500];
+ case ArticleSpecial.SpecialReportAlt:
+ return sourcePalette.specialReportAlt[200];
+ }
+};
+
// ----- Palette ----- //
/**
@@ -7129,6 +7162,10 @@ const paletteColours = {
light: crosswordTextLight,
dark: crosswordTextDark,
},
+ '--custom-subnav-header-text': {
+ light: customSubnavHeaderTextLight,
+ dark: customSubnavHeaderTextDark,
+ },
'--dateline': {
light: datelineLight,
dark: datelineDark,
diff --git a/dotcom-rendering/src/static/customsubnav/wc-desktop.png b/dotcom-rendering/src/static/customsubnav/wc-desktop.png
new file mode 100644
index 00000000000..d6be7e250aa
Binary files /dev/null and b/dotcom-rendering/src/static/customsubnav/wc-desktop.png differ
diff --git a/dotcom-rendering/src/static/customsubnav/wc-mobile.png b/dotcom-rendering/src/static/customsubnav/wc-mobile.png
new file mode 100644
index 00000000000..6742c2d0654
Binary files /dev/null and b/dotcom-rendering/src/static/customsubnav/wc-mobile.png differ
diff --git a/dotcom-rendering/src/static/customsubnav/wc-tablet.png b/dotcom-rendering/src/static/customsubnav/wc-tablet.png
new file mode 100644
index 00000000000..017e643d363
Binary files /dev/null and b/dotcom-rendering/src/static/customsubnav/wc-tablet.png differ
diff --git a/dotcom-rendering/src/static/customsubnav/wc-wide.png b/dotcom-rendering/src/static/customsubnav/wc-wide.png
new file mode 100644
index 00000000000..3eb86e41aa3
Binary files /dev/null and b/dotcom-rendering/src/static/customsubnav/wc-wide.png differ
diff --git a/dotcom-rendering/src/types/customSubnav.ts b/dotcom-rendering/src/types/customSubnav.ts
index 7bac6e7d85e..d811bce2233 100644
--- a/dotcom-rendering/src/types/customSubnav.ts
+++ b/dotcom-rendering/src/types/customSubnav.ts
@@ -2,6 +2,9 @@
* A custom subnav that can appear on assigned pages (e.g. fronts, articles). It arrives inside the `nav` object of the request payload.
*/
+/** The kind of page a custom subnav is rendered on, used to vary its styling. */
+export type RenderingPage = 'front' | 'article';
+
export interface CustomSubnavLink {
linkText: string;
dotcomPath: string;
@@ -30,3 +33,9 @@ export interface CustomSubnav {
pages: CustomSubnavTargetedPage[];
images?: CustomSubnavImage[];
}
+
+/** A custom subnav bundled with the type of page it's rendered on */
+export interface CustomSubnavForPage {
+ data: CustomSubnav;
+ renderingPage: RenderingPage;
+}