Agents (llms.txt)

Text with Media

Two-column section pairing copy with an image or video. Sides can be flipped.

Compose

Blocks, not lock-in

Every block ships its own source. Copy it, edit it, own it.

media slot
import { TextWithMedia } from '@/foundations/blocks/text-with-media';
import { Button } from '@/components/button';

export default function TextWithMediaPreview() {
  return (
    <TextWithMedia
      eyebrow="Compose"
      title="Blocks, not lock-in"
      description="Every block ships its own source. Copy it, edit it, own it."
      action={<Button>Browse the catalog</Button>}
      media={
        <div className="grid aspect-video w-full place-items-center text-foreground-secondary/40 text-sm">
          media slot
        </div>
      }
    />
  );
}

export const meta = {
  layout: 'fullscreen',
};

Dependencies

Source Code

import { Container } from '@/components/container';
import { cn } from '@/lib/utils/classnames';

interface TextWithMediaProps
  extends Omit<React.ComponentPropsWithRef<'section'>, 'title'> {
  eyebrow?: React.ReactNode;
  title: React.ReactNode;
  description?: React.ReactNode;
  action?: React.ReactNode;
  /** Media slot — image, video, or any node. Rendered in the opposite half. */
  media: React.ReactNode;
  /** Flip the column order — media on the right by default, on the left when reversed. */
  reversed?: boolean;
}

const TextWithMedia = ({
  ref,
  className,
  eyebrow,
  title,
  description,
  action,
  media,
  reversed,
  ...rest
}: TextWithMediaProps) => (
  <section
    ref={ref}
    className={cn('w-full py-16 md:py-24', className)}
    {...rest}
  >
    <Container>
      <div
        className={cn(
          'flex flex-col gap-10 lg:flex-row lg:items-center lg:gap-16',
          reversed && 'lg:flex-row-reverse'
        )}
      >
        <div className="flex flex-col gap-4 lg:w-1/2">
          {eyebrow && (
            <p className="font-medium text-foreground-secondary text-sm uppercase tracking-wider">
              {eyebrow}
            </p>
          )}
          <h3 className="text-balance text-4xl md:text-5xl">{title}</h3>
          {description && (
            <p className="text-pretty text-2xl text-foreground-secondary">
              {description}
            </p>
          )}
          {action && <div className="mt-2">{action}</div>}
        </div>

        <div className="lg:w-1/2">
          <div className="overflow-hidden rounded-xl bg-surface-sunken">
            {media}
          </div>
        </div>
      </div>
    </Container>
  </section>
);

export type { TextWithMediaProps };
export { TextWithMedia };

TextWithMedia puts a column of copy beside a column of media. Use reversed to alternate sides between consecutive blocks — the signature “zig-zag” pattern on long marketing pages.

Anatomy


          <TextWithMedia
  eyebrow="Compose"
  title="Blocks, not lock-in"
  description="Every block ships its own source. Copy it, edit it, own it."
  media={<img src="..." alt="" />}
/>
        

API Reference

Extends the section element.

Prop Default Type
eyebrow - ReactNode
title * - ReactNode
description - ReactNode
action - ReactNode
media * - ReactNode
reversed - boolean

Examples

Default

Compose

Blocks, not lock-in

Every block ships its own source. Copy it, edit it, own it.

media slot
import { TextWithMedia } from '@/foundations/blocks/text-with-media';
import { Button } from '@/components/button';

export default function TextWithMediaPreview() {
  return (
    <TextWithMedia
      eyebrow="Compose"
      title="Blocks, not lock-in"
      description="Every block ships its own source. Copy it, edit it, own it."
      action={<Button>Browse the catalog</Button>}
      media={
        <div className="grid aspect-video w-full place-items-center text-foreground-secondary/40 text-sm">
          media slot
        </div>
      }
    />
  );
}

export const meta = {
  layout: 'fullscreen',
};

Previous

Steps

Next

Changelog