Sections

Text and text

Two-column text section of paired text tiles, with surface enums and min-height tokens. Each column is one TextTile record (a surface, a SectionTitle's content, and a buttonGroup), so a CMS query spreads straight in.

Install

npx shadcn@latest add @alix/textAndText

Preview

How we work

Usage

import { TextAndText, type TextAndTextProps } from "@/components/textAndText";

const fromCMS: TextAndTextProps = await getBlock("approach-pair");

<TextAndText {...fromCMS} />

<TextAndText
  minHeight="md"
  contentLeft={{
    bgColor: "primary",
    sectionTitle: {
      title: "How we work",
      description: "Senior operators stay close to the decision and the data.",
      align: "start",
    },
    buttonGroup: {
      primaryAction: {
        children: "Our approach",
        rightIcon: <ArrowRight />,
        render: <a href="/about/how-we-work" />,
      },
    },
  }}
  contentRight={{
    bgColor: "greyGradient",
    sectionTitle: {
      variant: "largeText",
      description: "Clarity, pace, and practical execution when outcomes matter.",
      align: "start",
    },
    buttonGroup: {},
  }}
/>

Examples

01

Two titled columns

Both columns carry a titled SectionTitle on different surfaces: a sand tile beside a secondary one, each with its own action.

What we do

How we engage

<TextAndText
  minHeight="md"
  contentLeft={{
    bgColor: "sand",
    sectionTitle: {
      title: "What we do",
      description: "Turnaround, performance, transactions, and disputes.",
      align: "start",
    },
    buttonGroup: {
      primaryAction: {
        children: "Our services",
        rightIcon: <ArrowRight />,
        render: <a href="/services" />,
      },
    },
  }}
  contentRight={{
    bgColor: "secondary",
    sectionTitle: {
      title: "How we engage",
      description: "A senior-led diagnostic, then a team that stays through delivery.",
      align: "start",
    },
    buttonGroup: {
      primaryAction: {
        children: "How we work",
        rightIcon: <ArrowRight />,
        render: <a href="/about/how-we-work" />,
      },
    },
  }}
/>
02

A primary and secondary action

One tile can pair two actions. The secondary path sits beside the primary, and the variants stay the block's per-surface opinion.

Talk to the team

<TextAndText
  minHeight="md"
  contentLeft={{
    bgColor: "primary",
    sectionTitle: {
      title: "Talk to the team",
      description: "Start a conversation, or arrange an introductory call.",
      align: "start",
    },
    buttonGroup: {
      primaryAction: {
        children: "Contact us",
        rightIcon: <ArrowRight />,
        render: <a href="/contact" />,
      },
      secondaryAction: {
        children: "Schedule a call",
        render: <a href="/contact/call" />,
      },
    },
  }}
  contentRight={{
    bgColor: "greyGradient",
    sectionTitle: {
      variant: "largeText",
      description: "Situations move quickly. We're set up to move with them.",
      align: "start",
    },
    buttonGroup: {},
  }}
/>
03

Reverse the emphasis

Set reverse to lead with the statement on desktop while the source order stays intact on mobile.

The detail

<TextAndText
  minHeight="md"
  reverse
  contentLeft={{
    bgColor: "sand",
    sectionTitle: {
      title: "The detail",
      description: "Diagnostics, cash management, and operational delivery in one plan.",
      align: "start",
    },
    buttonGroup: {},
  }}
  contentRight={{
    bgColor: "primary",
    sectionTitle: {
      variant: "largeText",
      description: "Practical execution for situations where the outcome matters.",
      align: "start",
    },
    buttonGroup: {},
  }}
/>

When to use it

Use it for

  • Two text tiles side by side: a titled section beside a single statement, or two titled blocks on paired surfaces.
  • Contrasting a claim with its detail, each on its own surface, without any media.
  • CMS-fed sections where both columns' surface, content, and actions vary per record.

Reach for something else

  • A text tile beside an image or video: use TextAndMedia.
  • A single tile on its own: use TextTileDefault directly.
  • A bespoke button variant or a custom element inside a column: compose Grid and the textTile primitives directly.

Props

PropTypeDefaultDescription
contentLeft / contentRightTextTileDefaultPropsThe two independent text-tile records rendered side by side: each a surface (bgColor), a sectionTitle, and a buttonGroup.
sectionTitleSectionTitlePropsEach tile's content: tagline, title, description, align, and headingSize/headingTag. Set variant: "largeText" for the single bold statement (description only, sized up).
bgColor"default" | "transparent" | "primary" | "secondary" | "sand" | "greyGradient" | "goldGradient"Surface treatment for each text tile.
buttonGroupTileButtonGroupPropsUp to two actions per tile: primaryAction / secondaryAction as Button props minus variant. The tile picks the variant for the surface, so a record can't misconfigure it.
reversebooleanfalseSwaps the desktop column order; mobile keeps source order.
minHeight"none" | "md" | "lg" | "xl" | "2xl""lg"Minimum section height from the shared scale.
idstringOptional anchor id for in-page navigation.
escape hatchcompose lower layerFor custom elements, handlers, or a bespoke button variant, compose Grid and TextTileDefault directly.

See also