Patterns

Post row block

View in Figma

A row of one to four content cards from flat article records, with a column-count enum. CMS-serializable.

Install

npx shadcn@latest add @alix/postRowBlock

Preview

Usage

import PostRowBlock, { type PostRowProps } from "@/components/postRowBlock";

const fromCMS: PostRowProps = await getPostRow("latest-insights");

<PostRowBlock {...fromCMS} />

<PostRowBlock
  columns={3}
  contentCards={[
    {
      media: { type: "image", src: "/article-placeholder.svg", alt: "" },
      alignment: "vertical",
      title: "Revenue actions that protect the next quarter",
      excerpt: "A field note on pricing, mix, and execution discipline.",
      href: "/insights/revenue-actions",
    },
  ]}
/>

Examples

01

Two editorial columns

Set columns to 2 and give each card the editorial variant when the titles and excerpts should carry the weight.

<PostRowBlock
  columns={2}
  contentCards={[
    {
      variant: "editorial",
      media: { src: "/brand/editorial-1.jpg", alt: "" },
      alignment: "vertical",
      title: "Port disruption is now a working-capital issue",
      excerpt: "Leaders are connecting freight reliability, inventory exposure, and near-term cash decisions.",
      href: "/insights/port-disruption",
      titleAs: "h2",
    },
    {
      variant: "editorial",
      media: { src: "/brand/construction.webp", alt: "" },
      alignment: "vertical",
      title: "Capital projects need clearer control points",
      excerpt: "Where owners can reduce schedule risk before cost pressure compounds.",
      href: "/insights/capital-controls",
      titleAs: "h2",
    },
  ]}
/>
02

Four across, with a video card

A four-column row scans as a compact index; one card uses a video media type (type: "video") to show the discriminant per card.

<PostRowBlock
  columns={4}
  contentCards={[
    { media: { type: "image", src: "/brand/editorial-1.jpg", alt: "" }, alignment: "vertical", title: "Liquidity planning when cash windows compress", excerpt: "…", href: "/insights/liquidity-planning" },
    { media: { type: "video", src: "/media/turnaround-loop.mp4", poster: "/brand/ship.webp", muted: true, loop: true }, alignment: "vertical", title: "Turnaround delivery in motion", excerpt: "…", href: "/insights/turnaround-delivery" },
    { media: { type: "image", src: "/brand/editorial-2.jpg", alt: "" }, alignment: "vertical", title: "Diligence findings that translate to value", excerpt: "…", href: "/insights/diligence-to-value" },
    { media: { type: "image", src: "/brand/people.webp", alt: "" }, alignment: "vertical", title: "Board reporting under pressure", excerpt: "…", href: "/insights/board-reporting" },
  ]}
/>

When to use it

Use it for

  • A single CMS-fed row of related articles, e.g. "latest insights", with the column count set by content.
  • Editorial rows where each title and excerpt should read as the lead, not a category-led item.
  • Reusing the same row renderer a PostGrid uses, so a standalone row and an index page can't drift apart.

Reach for something else

  • A full paginated index of posts — use PostGrid instead.
  • One teaser card on its own — use ContentCard instead.
  • Custom spacing or row semantics — call renderPostRow inside your own section frame instead.

Props

PropTypeDefaultDescription
contentCardsContentCardProps[]Flat CMS article/card records passed to ContentCard in the shared row renderer.
columns1 | 2 | 3 | 4Required row column count supplied by the CMS.
ContentCardProps.variant"news" | "editorial""news"Card visual variant.
ContentCardProps.media.type"image" | "video""image"Media discriminant for each card.
ContentCardProps.alignment"vertical" | "horizontal"Required card media/text orientation.
ContentCardProps.titleAs"h2" | "h3" | "h4""h3"Semantic heading level for each card title.
escape hatchcompose lower layerFor custom spacing or row semantics, call `renderPostRow` inside your own section frame.

See also