Skip to main content
Gremorie

Card

Composable surface for grouping related content. Headers, descriptions, content slots, and footers - opt in to the parts you need.

Overview

Card is the cornerstone display primitive: a rounded surface with a structured Header to Content to Footer rhythm. Each sub-component is a thin styled div with a data-slot attribute, so the API stays composable rather than configurative. You opt in to the parts you need: a marketing card might use only CardHeader and CardContent; a dashboard tile might add CardAction for a top-right control and CardFooter for a trend caption.

Card is intentionally surface-only. It doesn't manage focus, hover, or selection - wrap it in a Button (via asChild) or a Link when the whole card should be interactive.

Preview

Welcome to Gremorie
AI-native design system, registry first.

Compose primitives directly. Skip the lock-in.

'use client';import {  Card,  CardContent,  CardDescription,  CardFooter,  CardHeader,  CardTitle,} from '@gremorie/rx-display';import { Button } from '@gremorie/rx-forms';export function CardPreview() {  return (    <Card className="max-w-sm">      <CardHeader>        <CardTitle>Welcome to Gremorie</CardTitle>        <CardDescription>          AI-native design system, registry first.        </CardDescription>      </CardHeader>      <CardContent>        <p className="text-sm text-muted-foreground">          Compose primitives directly. Skip the lock-in.        </p>      </CardContent>      <CardFooter>        <Button>Get started</Button>      </CardFooter>    </Card>  );}

Anatomy

Card                       the surface (bg-card, rounded, bordered, vertical flow)
├─ CardHeader              title/description area; lays out a CardAction to the right when present
│  ├─ CardTitle            semibold heading line
│  ├─ CardDescription      muted supporting copy
│  └─ CardAction           top-right slot (button, menu, switch)
├─ CardContent             the main body (padded horizontally)
└─ CardFooter              bottom row (actions, captions, metadata)

Installation

bash npx gremorie@latest add rx-card
bash pnpm dlx gremorie@latest add rx-card
bash yarn dlx gremorie@latest add rx-card
bash bunx --bun gremorie@latest add rx-card

Usage

import {
  Card,
  CardHeader,
  CardTitle,
  CardDescription,
  CardContent,
  CardFooter,
} from "@gremorie/rx-display";
import { Button } from "@gremorie/rx-forms";

export function Example() {
  return (
    <Card className="max-w-sm">
      <CardHeader>
        <CardTitle>Welcome to Gremorie</CardTitle>
        <CardDescription>AI-native design system, registry first.</CardDescription>
      </CardHeader>
      <CardContent>
        <p className="text-sm text-muted-foreground">
          Compose primitives directly. Skip the lock-in.
        </p>
      </CardContent>
      <CardFooter>
        <Button>Get started</Button>
      </CardFooter>
    </Card>
  );
}
npx gremorie@latest add ng-card
import { Component } from '@angular/core';
import {
  Card,
  CardHeader,
  CardTitle,
  CardDescription,
  CardContent,
  CardFooter,
} from '@gremorie/ng-display';
import { Button } from '@gremorie/ng-forms';

@Component({
  selector: 'app-example',
  standalone: true,
  imports: [
    Card,
    CardHeader,
    CardTitle,
    CardDescription,
    CardContent,
    CardFooter,
    Button,
  ],
  template: `
    <gr-card class="max-w-sm">
      <gr-card-header>
        <gr-card-title>Welcome to Gremorie</gr-card-title>
        <gr-card-description
          >AI-native design system, registry first.</gr-card-description
        >
      </gr-card-header>
      <gr-card-content>
        <p class="text-sm text-muted-foreground">
          Compose primitives directly. Skip the lock-in.
        </p>
      </gr-card-content>
      <gr-card-footer>
        <ai-button>Get started</ai-button>
      </gr-card-footer>
    </gr-card>
  `,
})
export class ExampleComponent {}

API

<Card>

The root surface. Renders a rounded div with border, background, and a flex flex-col gap-6 rhythm.

PropTypeDefaultDescription
classNamestring-Extra classes merged via cn. Use for layout (width, max-width, margin), not for surface colors.

All other div props are forwarded.

<CardHeader>

Container for the title, description, and optional action. Uses a grid so CardAction can sit top-right while title and description stack on the left.

PropTypeDefaultDescription
classNamestring-Extra classes merged via cn.

<CardTitle>

Section heading. Renders as a div with leading-none font-semibold.

<CardDescription>

Secondary copy under the title. Renders as a div with text-muted-foreground text-sm.

<CardAction>

Top-right slot inside CardHeader for a control (icon button, menu trigger, badge). Uses CSS grid positioning, so the order in JSX doesn't matter - CardHeader detects it via has-data-[slot=card-action] and switches to a two-column layout.

<CardContent>

Main body. Horizontal padding only - vertical rhythm comes from the root gap-6.

<CardFooter>

Footer row. Defaults to flex items-center; add border-t on CardFooter (or border-b on CardHeader) and the slot expands its vertical padding via [.border-t]:pt-6.

Composition

  1. <Card> is the surface. Always the outermost element.
  2. <CardHeader> holds CardTitle, CardDescription, and optionally CardAction.
  3. <CardContent> is the body. Drop in paragraphs, charts, lists, forms.
  4. <CardFooter> holds actions or summary text. Optional.

Use the full composition rather than dumping everything into CardContent - the named slots give downstream consumers clear hooks to override or theme.

Variations

Card with action

Monthly revenue
Across all active workspaces.
+12.4%

$48,210

'use client';import {  Badge,  Card,  CardAction,  CardContent,  CardDescription,  CardHeader,  CardTitle,} from '@gremorie/rx-display';export function CardWithActionPreview() {  return (    <Card className="max-w-sm">      <CardHeader>        <CardTitle>Monthly revenue</CardTitle>        <CardDescription>Across all active workspaces.</CardDescription>        <CardAction>          <Badge variant="secondary">+12.4%</Badge>        </CardAction>      </CardHeader>      <CardContent>        <p className="text-2xl font-semibold">$48,210</p>      </CardContent>    </Card>  );}

CardAction sits in the top-right of CardHeader via CSS grid - JSX order does not matter. CardHeader detects it through has-data-[slot=card-action] and switches to a two-column layout.

Invite teammates
They will get an email with a join link.

Up to 10 seats are included on the Pro plan.

'use client';import {  Card,  CardContent,  CardDescription,  CardFooter,  CardHeader,  CardTitle,} from '@gremorie/rx-display';import { Button } from '@gremorie/rx-forms';export function CardWithFooterPreview() {  return (    <Card className="max-w-sm">      <CardHeader>        <CardTitle>Invite teammates</CardTitle>        <CardDescription>          They will get an email with a join link.        </CardDescription>      </CardHeader>      <CardContent>        <p className="text-sm text-muted-foreground">          Up to 10 seats are included on the Pro plan.        </p>      </CardContent>      <CardFooter className="border-t">        <Button variant="outline" className="w-full">          Send invites        </Button>      </CardFooter>    </Card>  );}

The border-t on CardFooter triggers [.border-t]:pt-6, expanding the footer's top padding so the separator reads cleanly.

Accessibility

  • Structural, not semantic by default: Card renders as a plain div. It is not announced as a region.
  • Add role="region" and aria-labelledby when the card represents a distinct section that should be navigable as a landmark (e.g. a dashboard widget). Point aria-labelledby at the CardTitle's id.
  • Make the whole card clickable by wrapping CardHeader's title in an <a> and stretching with ::after, or by wrapping the entire Card in a Button via asChild patterns. Don't add onClick to the bare Card - that's a div without keyboard support.
  • Focus order follows DOM order; CardAction is positioned via grid, so it visually sits top-right but receives focus in the order it appears in JSX (after the title and description, by default).
  • Badge - common content for CardAction.
  • Button - typical action in CardFooter.
  • Separator - dividers inside dense cards.
  • Table - frequent body content for data cards.

On this page