Skip to main content

Card

Group related content and actions on a neutral surface.

Card groups related content and actions on a neutral surface. Its parts are structural and add no interactive behavior or ARIA relationships. Card.Title renders a div by default; choose a heading with as when the page needs one.

Basic usage#

import { Card } from 'moraine'

export function Example() {
  return (
    <Card>
      <Card.Header>
        <Card.Title>Project</Card.Title>
      </Card.Header>
      <Card.Body>Project details</Card.Body>
    </Card>
  )
}

Playground#

Team Subscription

Manage billing and team seat limits.

Pro Plan

Your team has 8 active seats out of 10 available. Upgrade to Enterprise for unlimited seats and SSO integration.

$49 / month
Props
Slots

Anatomy#

Card [component; slot=root]
├── Card.Header [part; slot=header]
│   ├── Card.Title [part; slot=title]
│   ├── Card.Description [part; slot=description]
│   └── Card.Action [part; slot=action]
├── Card.Body [part; slot=body]
└── Card.Footer [part; slot=footer]

Usage#

Variants and sizes#

Every variant has an outer border, and the header keeps the same appearance as the body. outline adds a top border to the footer. subtle adds the footer divider and a muted footer background. none keeps only the outer border and the anatomy spacing. The sm, md, and lg sizes change the density of the header, body, footer, title, and description together.

Compose the parts you need inside Card. Header and body provide horizontal insets, while the root spaces the sections. Direct children can still span the card’s full width.

Examples#

Structured content#

import { Button, Card } from 'moraine'

export function CardStructure() {
  return (
    <Card class="max-w-sm w-full">
      <Card.Header>
        <Card.Title as="h3">Project Deployment</Card.Title>
        <Card.Description>Configure automated build triggers.</Card.Description>
      </Card.Header>
      <Card.Body>
        <p class="text-sm text-muted-foreground">
          Deployments are pushed directly to the globally distributed edge CDN network.
        </p>
      </Card.Body>
      <Card.Footer class="gap-2 justify-end">
        <Button variant="ghost" size="xs">
          Cancel
        </Button>
        <Button size="xs">Deploy</Button>
      </Card.Footer>
    </Card>
  )
}

Semantic heading#

Card does not choose a heading level. Use as="h2" or another appropriate level for the surrounding page.

import { Badge, Card } from 'moraine'

export function CardSemantics() {
  return (
    <Card as="article" class="max-w-sm w-full">
      <Card.Header>
        <Card.Title as="h3">Release v2.4.0 Available</Card.Title>
        <Card.Description>Published 2 hours ago by @release-bot</Card.Description>
        <Card.Action>
          <Badge>New</Badge>
        </Card.Action>
      </Card.Header>
      <Card.Body>
        <p class="text-sm text-muted-foreground">
          Includes performance optimizations, smaller bundle sizes, and refreshed component styles.
        </p>
      </Card.Body>
    </Card>
  )
}

Full-bleed media#

Place media directly inside Card to span its width. Apply clipping on the root only if the composition needs it.

import { Button, Card } from 'moraine'

export function WithImage() {
  return (
    <Card class="max-w-sm w-full overflow-hidden">
      <img
        src="https://images.unsplash.com/photo-1604076850742-4c7221f3101b?q=80&w=1887&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
        alt="Landscape by mymind on Unsplash"
        class="w-full aspect-video object-cover brightness-60 grayscale"
      />
      <Card.Body>
        <Card.Title as="h3">Beautiful Landscape</Card.Title>
        <Card.Description class="mt-1">
          A full-bleed image can sit directly inside Card.
        </Card.Description>
      </Card.Body>
      <Card.Footer>
        <Button class="w-full">Open</Button>
      </Card.Footer>
    </Card>
  )
}

Header action#

import { Button, Card } from 'moraine'

export function HeaderAction() {
  return (
    <Card class="w-lg">
      <Card.Header>
        <Card.Title as="h3">Meeting Notes</Card.Title>
        <Card.Description>Transcript summary from the latest client sync.</Card.Description>
        <Card.Action>
          <Button size="sm" variant="secondary">
            Transcribe
          </Button>
        </Card.Action>
      </Card.Header>
      <Card.Body>
        <ol class="text-sm pl-5 list-decimal opacity-85 flex flex-col gap-1.5">
          <li>Dashboard redesign should prioritize mobile layouts.</li>
          <li>Timeline target is six weeks with weekly milestones.</li>
          <li>Next review meeting is scheduled for Tuesday morning.</li>
        </ol>
      </Card.Body>
      <Card.Footer class="justify-end">
        <Button size="sm" variant="outline">
          Dismiss
        </Button>
        <Button size="sm">Save</Button>
      </Card.Footer>
    </Card>
  )
}

Custom header#

import { Card } from 'moraine'

export function CustomHeader() {
  return (
    <Card class="max-w-sm">
      <Card.Header class="flex items-center justify-between">
        <div>
          <p class="text-xs text-muted-foreground">Storage</p>
          <p class="text-2xl font-semibold">72%</p>
        </div>
        <span class="text-success text-xs font-medium">12 GB free</span>
      </Card.Header>
      <Card.Body>
        <p class="text-sm text-muted-foreground">Archive old uploads to reclaim more space.</p>
      </Card.Body>
    </Card>
  )
}

Props#

Card#

Props for Card. Renders a <div> element by default.

Prop

Header#

Renders a <div> element by default.

Prop

Title#

Renders a <div> element by default.

Prop

Description#

Renders a <p> element by default.

Prop

Action#

Renders a <div> element by default.

Prop

Body#

Renders a <div> element by default.

Prop

Renders a <div> element by default.

Prop