Patterns

Sections a content team can’t get wrong.

Components expose slots for composition. Sections are the deliberate inverse — opinionated page sections with flat, serializable data props that spread straight from a CMS query. Every decision the section makes is one the handoff can’t misconfigure.

Components

For engineers

ReactNode slots, rich APIs, full composability. Build anything.

<SectionTitle
  title="Plans for every team"
  primaryAction={<Button>Get started</Button>}
/>

Sections

For content teams

Flat data props, enums, and name-references. Spread and ship.

<TextAndMediaBlock
  variant="titleAndText"
  title="Plans for every team"
  media={{ src: cms.image }}
/>
01

Text + Text

@/components/textAndTextBlock

Two text tiles side by side, each its own surface — a titled section beside a single bold statement. The brand's call-and-response device.

CMS data
<TextAndTextBlock
  contentLeft={{
    variant: "titleAndText",
    bgColor: "primary",
    tagline: "Our approach",
    title: "Results. Fast. Together.",
    description: "Senior-led teams combine vision with action.",
  }}
  contentRight={{
    variant: "largeText",
    bgColor: "greyGradient",
    text: "Do we have the experience to help you? Yes.",
  }}
/>
Our approach

Results. Fast. Together.

Senior-led teams combine strategic vision and real-world experience with an explicit propensity to action.

Do we have the experience to help you? Yes.

02

Text + Media

@/components/textAndMediaBlock

A text tile beside a framed image or video. Media is a URL string, so a consumer's own image pipeline feeds it directly — the block stays CMS-agnostic.

CMS data
<TextAndMediaBlock
  variant="titleAndText"
  tagline="Text + media"
  title="One template, any CMS field."
  description="Flat props spread straight from a query."
  media={{ type: "image", src: "/hero.jpg", alt: "" }}
  ratio="16/9"
/>
Text + media

One template, any CMS field.

Flat props spread straight from a query — title, description, and a media URL. No SDK, no field mapping.

03

Post Row

@/components/postRowBlock

A responsive grid of content cards for an insights or news feed — pass an array of card data and a column count straight from the CMS.

CMS data
<PostRowBlock
  columns={3}
  contentCards={posts.map((p) => ({
    variant: "news",
    media: { type: "image", src: p.cover, alt: "" },
    tagline: p.category,
    title: p.title,
    excerpt: p.summary,
    href: `/insights/${p.slug}`,
  }))}
/>

Need behaviour a CMS can’t express — a click handler, a framework link, element-level image optimisation? Drop down to the slot components and compose directly. The block is the locked shell; the components are the engine.

Explore the components