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

Senior operators and financial experts stay close to the decision, the data, and the implementation plan.
Clarity, pace, and practical execution for situations where outcomes matter.

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

Turnaround, performance, transactions, and disputes — delivered by senior teams close to the decision.

How we engage

A senior-led diagnostic, then a delivery team that stays through implementation.
<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

Start a conversation about a situation you're facing, or arrange an introductory call.
Situations move quickly — we're set up to move with them.
<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

Diagnostics, cash management, and operational delivery, connected into one plan.
Practical execution for situations where the outcome matters.
<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