@v4/ghost-content

The editorial content-block components: article headers, rich text, promos, embeds and carousels, composed over ghost-core.

2026-11-07

Overview

@v4/ghost-content is the editorial content-block package — the components that make up an article body and the content-led pages around it: article and listing headers, rich-text wells, pull quotes, stats blocks, promos, social and gallery embeds, and the content carousels. It carries 72 components (the newest is header-block-feature) plus a small set of shared internal layouts under src/components/_internal/ (article-card-by-format, article-header, article-page, carousel-block, listing-header, listing-page) that several public components are built over.

It is a sibling of @v4/ghost-core, built exactly the same way — typed vanilla-extract themes, colour-set axis, 100% binding coverage — and it composes ghost-core rather than re-implementing anything: a content block that needs a button, badge or link imports the real one from @v4/ghost-core. Content components bind g.components.content.* contract paths (never raw tokens.components.content.*) so Euro League's overrides flow through.

Built from the ELB fork

In this repo the source of truth for every component is ELB – Web – Content (9iz5GeR9syO68cmPr1ppE1), ELB's fork of Ghost – Content 2.0 (RHCx7G92nx50KjNK6QshhU). The fork preserves the upstream node ids, so a bridge that still names the Ghost file resolves every id perfectly while measuring the wrong design — re-verify a bridge's fileKey, not just that its ids resolve. Why, and what follows for a component, is on Building against ELB's Figma forks. Measured on 6 Nov 2026 from each component's *.figma.json: 59 bridges name ELB – Web – Content, 12 still name Ghost – Content, and one, clips-block (the frame of the WSC Stories row), names ELB – Web – EXT (Yqy0zItrtL4UptOVW54wVg). node packages/token-parser/scripts/check-bridges.mjs is the offline gate.

For the representative-component tour with Storybook deep links, see the sibling-packages catalogue. For how these blocks are assembled into pages and mounted by the app, see @v4/content-app.

Entry points

Import Contents
@v4/ghost-content The 72 content components and their prop types / mock props
@v4/ghost-content/init.css Owner-keyed side-effect stylesheet emitting the package's --ph__* CSS variables
@v4/ghost-content/components/<name> (15) One component on its own — see below

The 15 per-component subpaths are accordion-block, block-scroller, download-block, gateway-carousel, promo-block-article-promo, promo-block-floating-image, promo-block-full-image, promo-block-simple, promo-block-supporting-image, promo-carousel, promo-countdown-banner, stats-block, tabbed-carousel, table-block and text-image-block (17 export keys in all, with . and ./init.css). They exist for import cost: the less common blocks are loaded with next/dynamic only by a page that renders them (packages/content-app/src/lib/lazy-blocks.tsx). Imported from the barrel, every page with any block carried all of them, about 63 KB gzip of script (25 Oct 2026).

How it is consumed

@v4/content-app composes these blocks into the CONTENT domain's page bodies (news article, listing, and so on). The app mounts those via generated shims. @v4/ghost-basketball, @v4/basketball-app and @v4/code-of-conduct compose content components too.

The package registers its themes through createPackageTheme with an explicit OWN_THEMES list in its init.css.ts. In the app, apps/web/src/theme/tokens.css.ts emits Euro League's values for ghost-core and ghost-content together on every page (see @v4/ghost-core) — the theming model is shared across the four Ghost packages in this repo: ghost-core, -content, -video and -basketball. See Component system and Theming and colour sets.