Card

Split Card

Rounded two-column card with copy on one side and a picture or scene on the other, stacking until the Section is 48rem wide. Flip swaps the sides and the hairline divider beside the media is optional.

The source comes with full access.

One payment of $149 ($49 for now) opens the source right here and lets the CLI install every paid component, with lifetime updates.

Installation

pnpm dlx shadcn add @01orbital/split-card
npx shadcn add @01orbital/split-card
yarn shadcn add @01orbital/split-card
bunx --bun shadcn add @01orbital/split-card

Part of the 01orbital library. With a license, put ORBITAL_TOKEN in .env.local and the same command installs it (licensed install, pricing).

It lands at components/card/split-card.tsx, as source you own and edit.

The example also uses button, poster and section. Install them too:

pnpm dlx shadcn add @01orbital/button @01orbital/poster @01orbital/section
npx shadcn add @01orbital/button @01orbital/poster @01orbital/section
yarn shadcn add @01orbital/button @01orbital/poster @01orbital/section
bunx --bun shadcn add @01orbital/button @01orbital/poster @01orbital/section

Usage

import type { ComponentProps } from "react"
import { cn } from "cn"

import {
  SplitCard,
  SplitCardActions,
  SplitCardContent,
  SplitCardDescription,
  SplitCardHeader,
  SplitCardMedia,
  SplitCardTitle,
} from "@/components/card/split-card"
import {
  Section,
  SectionContent,
} from "@/components/editorial/section"
import {
  Poster,
  PosterDescription,
  PosterFootnote,
  PosterLabel,
  PosterTitle,
} from "@/components/poster/poster"
import { buttonVariants } from "@/components/ui/button"

const COPY = {
  title: "The economics of delivering on a system",
  body: "A study of build time, review cycles and coherence across fifty launches.",
  cta: "Download",
  label: "01orbital.",
  posterTitle: "The economics of delivering on a system",
  posterBody: "Cost savings and coherence with the kit",
  footnote: "A study of fifty launches, 2026",
} as const

// report-download-01's art, copied here so the demo matches the block.
function OrbitRings({ className, ...props }: ComponentProps<"div">) {
  return (
    <div
      aria-hidden="true"
      className={cn(
        "pointer-events-none absolute inset-0 before:absolute before:top-[22%] before:left-[8%] before:size-44 before:rounded-full before:border before:border-foreground/25 after:absolute after:right-[6%] after:bottom-[10%] after:size-64 after:rounded-full after:border after:border-dashed after:border-foreground/25",
        className
      )}
      {...props}
    />
  )
}

function OrbitMark({ className, ...props }: ComponentProps<"span">) {
  return (
    <span
      aria-hidden="true"
      className={cn(
        "flex items-center gap-2 text-current before:size-8 before:rounded-full before:border before:border-current/80 after:size-12 after:rounded-full after:border after:border-current/80",
        className
      )}
      {...props}
    >
      <span className="w-10 border-t border-dashed border-current/80" />
    </span>
  )
}

export default function SplitCardDemo() {
  return (
    <Section>
      <SectionContent className="items-center">
        <SplitCard className="w-full max-w-5xl dark:surface-sheen">
          <SplitCardContent>
            <SplitCardHeader>
              <SplitCardTitle>{COPY.title}</SplitCardTitle>
              <SplitCardDescription>{COPY.body}</SplitCardDescription>
            </SplitCardHeader>
            <SplitCardActions>
              <a
                href="#"
                data-slot="button"
                className={cn(
                  buttonVariants({ variant: "secondary", size: "lg" })
                )}
              >
                {COPY.cta}
              </a>
            </SplitCardActions>
          </SplitCardContent>
          <SplitCardMedia divider={false}>
            <OrbitRings />
            <Poster className="dark rotate-3 text-shadow-legible">
              <img
                src="https://01orbital.dev/art/kit/layered-currents-415686.png"
                alt=""
                loading="lazy"
              />
              <PosterLabel>{COPY.label}</PosterLabel>
              <PosterTitle>{COPY.posterTitle}</PosterTitle>
              <PosterDescription>{COPY.posterBody}</PosterDescription>
              <OrbitMark className="mt-8" />
              <PosterFootnote>{COPY.footnote}</PosterFootnote>
            </Poster>
          </SplitCardMedia>
        </SplitCard>
      </SectionContent>
    </Section>
  )
}

Anatomy

Split Card is built from 7 parts. In the example they nest like this:

SplitCard
├─ SplitCardContent
│  ├─ SplitCardHeader
│  │  ├─ SplitCardTitle
│  │  └─ SplitCardDescription
│  └─ SplitCardActions
└─ SplitCardMedia
  • SplitCard: A wide card with copy on one side and a picture or scene on the other. Put a SplitCardContent and a SplitCardMedia inside.
  • SplitCardContent: The copy half of a SplitCard, holding a SplitCardHeader and SplitCardActions. It centers its contents vertically, and its padding grows as the section widens.
  • SplitCardHeader: Groups the SplitCardTitle and SplitCardDescription and caps their width at 28rem.
  • SplitCardTitle: The card's title, an h3 in the display font that steps up a size when the section is 48rem wide. Pass render to use another heading level.
  • SplitCardDescription: Muted supporting text under the SplitCardTitle.
  • SplitCardActions: A row for the card's buttons or links; it wraps when they do not fit.
  • SplitCardMedia: The picture half of a SplitCard. It is at least 24rem tall, centers whatever you put in it, and clips anything that spills over.

API reference

SplitCard

A wide card with copy on one side and a picture or scene on the other. Put a SplitCardContent and a SplitCardMedia inside.

It responds to the width of the enclosing Section, not the screen. The two halves stack until the section is 48rem wide, then sit side by side, and the padding, title size and divider change at the same width. Outside a Section, the halves always stack.

Prop

Type

It also accepts the HTML attributes of the element it renders.

SplitCardContent

The copy half of a SplitCard, holding a SplitCardHeader and SplitCardActions. It centers its contents vertically, and its padding grows as the section widens.

It accepts the HTML attributes of the element it renders.

SplitCardHeader

Groups the SplitCardTitle and SplitCardDescription and caps their width at 28rem.

It accepts the HTML attributes of the element it renders.

SplitCardTitle

The card's title, an h3 in the display font that steps up a size when the section is 48rem wide. Pass render to use another heading level.

It accepts the props of the Base UI part it wraps, and the HTML attributes of the element it renders.

SplitCardDescription

Muted supporting text under the SplitCardTitle.

It accepts the HTML attributes of the element it renders.

SplitCardActions

A row for the card's buttons or links; it wraps when they do not fit.

It accepts the HTML attributes of the element it renders.

SplitCardMedia

The picture half of a SplitCard. It is at least 24rem tall, centers whatever you put in it, and clips anything that spills over.

A scene fills it with absolute inset-0; an img needs size-full object-cover to fill it.

Prop

Type

It also accepts the HTML attributes of the element it renders.

Dependencies

npm: @base-ui/react, cn. The CLI installs them for you.

Registry pieces it installs with it:

  • font-hanken-grotesk