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.

<TextAndMedia
  textTileVariant="titleAndText"
  textTile={{
    bgColor: "default",
    sectionTitle: { title: "Plans for every team" },
    buttonGroup: {},
  }}
  media={{ src: cms.image }}
/>
01

Text + Text

@/components/textAndText

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
<TextAndText
  contentLeft={{
    bgColor: "primary",
    sectionTitle: {
      tagline: { text: "Our approach" },
      title: "Results. Fast. Together.",
      description: "Senior-led teams combine vision with action.",
      align: "start",
    },
    buttonGroup: {},
  }}
  contentRight={{
    bgColor: "greyGradient",
    sectionTitle: {
      variant: "largeText",
      description: "Do we have the experience to help you? Yes.",
      align: "start",
    },
    buttonGroup: {},
  }}
/>
Our approach

Results. Fast. Together.

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 and the block stays CMS-agnostic.

CMS data
<TextAndMedia
  textTileVariant="titleAndText"
  textTile={{
    bgColor: "default",
    sectionTitle: {
      tagline: { text: "Text + media" },
      title: "One template, any CMS field.",
      description: "Flat props spread straight from a query.",
      align: "start",
    },
    buttonGroup: {},
  }}
  media={{ type: "image", src: "/hero.jpg", alt: "" }}
  ratio="16/9"
/>
Text + media

One template, any CMS field.

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",
    alignment: "vertical",
    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