component

Card

A versatile card component for grouping related content. Card uses the Panel core component as its surface layer, supporting the same theming system — feedback colors, background styles, border, and radius control — while adding a structured layout with optional header (heading, subheading, leading, trailing), a content area, and an optional footer.

Default

surface.soft

Card with surface.soft background

Content area for the surface.soft background variant.

surface.step.1

Card with surface.step.1 background

Content area for the surface.step.1 background variant.

surface.step.2

Card with surface.step.2 background

Content area for the surface.step.2 background variant.

surface.step.3

Card with surface.step.3 background

Content area for the surface.step.3 background variant.

surface.deep

Card with surface.deep background

Content area for the surface.deep background variant.

transparent

Card with transparent background

Content area for the transparent background variant.

---
import Card from "../Card.astro";
import { stack } from "@pindoba/styled-system/patterns";
import { css } from "@pindoba/styled-system/css";

const backgrounds = [
  "surface.soft",
  "surface.step.1",
  "surface.step.2",
  "surface.step.3",
  "surface.deep",
  "transparent",
] as const;
---

<div class={stack({ gap: "md", direction: "column", width: "100%" })}>
  {
    backgrounds.map((bg) => (
      <Card
        background={bg}
        header={{
          heading: bg,
          subheading: `Card with ${bg} background`,
        }}
      >
        <p class={css({ margin: "0" })}>
          Content area for the <strong>{bg}</strong> background variant.
        </p>
      </Card>
    ))
  }
</div>

Feedback

Cards support semantic feedback colors. The heading, subheading, border, and background all follow the selected color palette.

neutral

Card with neutral feedback

Heading and subheading colors follow the neutral palette.

primary

Card with primary feedback

Heading and subheading colors follow the primary palette.

success

Card with success feedback

Heading and subheading colors follow the success palette.

warning

Card with warning feedback

Heading and subheading colors follow the warning palette.

danger

Card with danger feedback

Heading and subheading colors follow the danger palette.

---
import Card from "../Card.astro";
import { stack } from "@pindoba/styled-system/patterns";
import { css } from "@pindoba/styled-system/css";

const feedbacks = [
  "neutral",
  "primary",
  "success",
  "warning",
  "danger",
] as const;
---

<div class={stack({ gap: "md", direction: "column", width: "100%" })}>
  {
    feedbacks.map((feedback) => (
      <Card
        feedback={feedback}
        border="default"
        header={{
          heading: feedback,
          subheading: `Card with ${feedback} feedback`,
        }}
      >
        <p class={css({ margin: "0" })}>
          Heading and subheading colors follow the <strong>{feedback}</strong>{" "}
          palette.
        </p>
      </Card>
    ))
  }
</div>

Size

The size prop controls the padding of the header, content, and footer sections, as well as the heading font size.

Small

Small Card

Compact padding and font sizes

Small card body content.

Footer
Action

Medium (default)

Medium Card

Default padding and font sizes

Medium card body content.

Footer
Action

Large

Large Card

Generous padding and font sizes

Large card body content.

Footer
Action
---
import Card from "../Card.astro";
import { stack } from "@pindoba/styled-system/patterns";
import { css } from "@pindoba/styled-system/css";
---

<div class={stack({ gap: "lg", direction: "column", width: "100%" })}>
  <div>
    <h3>Small</h3>
    <Card
      size="sm"
      header={{
        heading: "Small Card",
        subheading: "Compact padding and font sizes",
      }}
      footer={{ leading: "Footer", trailing: "Action" }}
    >
      <p class={css({ margin: "0" })}>Small card body content.</p>
    </Card>
  </div>

  <div>
    <h3>Medium (default)</h3>
    <Card
      size="md"
      header={{
        heading: "Medium Card",
        subheading: "Default padding and font sizes",
      }}
      footer={{ leading: "Footer", trailing: "Action" }}
    >
      <p class={css({ margin: "0" })}>Medium card body content.</p>
    </Card>
  </div>

  <div>
    <h3>Large</h3>
    <Card
      size="lg"
      header={{
        heading: "Large Card",
        subheading: "Generous padding and font sizes",
      }}
      footer={{ leading: "Footer", trailing: "Action" }}
    >
      <p class={css({ margin: "0" })}>Large card body content.</p>
    </Card>
  </div>
</div>

With Buttons and Icons

The card’s header and footer slots accept rich content. Place an icon in the leading position to help users identify the card’s category at a glance, add a ghost button to trailing for contextual actions like settings or a menu, and use the footer trailing for primary and secondary action buttons.

Icon in Header

Document

Last edited 2 hours ago

Draft

An icon in the header leading slot helps users quickly identify the card's category or type at a glance.

Button in Header

Project Settings

Configure your project preferences

Beta

A ghost icon button in the header trailing slot is a common pattern for contextual actions like editing, sharing, or opening a menu.

Buttons in Footer

User Profile

Update your personal information

Active

Action buttons in the footer trailing slot follow a standard confirm/cancel pattern, keeping primary actions clearly separated from the card's content.

---
import Card from "../Card.astro";
import Button from "@pindoba/astro-button";
import Badge from "@pindoba/astro-badge";
import { Icon } from "astro-icon/components";
import { stack, flex } from "@pindoba/styled-system/patterns";
import { css } from "@pindoba/styled-system/css";
---

<div class={stack({ gap: "xl", direction: "column" })}>
  <div>
    <h3>Icon in Header</h3>
    <Card
      header={{
        heading: "Document",
        subheading: "Last edited 2 hours ago",
      }}
    >
      <Icon name="lucide:file-text" size={20} slot="heading-leading" />
      <Icon name="lucide:clock" size={14} slot="subheading-leading" />
      <Badge feedback="warning" slot="header-trailing">
        <Icon name="lucide:pencil" class={css({ w: "1em", h: "1em" })} />
        Draft
      </Badge>
      <p class={css({ margin: "0" })}>
        An icon in the header leading slot helps users quickly identify the
        card's category or type at a glance.
      </p>
    </Card>
  </div>

  <div>
    <h3>Button in Header</h3>
    <Card
      header={{
        heading: "Project Settings",
        subheading: "Configure your project preferences",
      }}
    >
      <Icon name="lucide:settings" size={20} slot="header-leading" />
      <div class={flex({ gap: "sm", align: "center" })} slot="header-trailing">
        <Badge feedback="primary" emphasis="secondary">
          <Icon
            name="lucide:flask-conical"
            class={css({ w: "1em", h: "1em" })}
          />
          Beta
        </Badge>
        <Button emphasis="ghost" shape="circle">
          <Icon name="lucide:ellipsis" />
        </Button>
      </div>
      <p class={css({ margin: "0" })}>
        A ghost icon button in the header trailing slot is a common pattern for
        contextual actions like editing, sharing, or opening a menu.
      </p>
    </Card>
  </div>

  <div>
    <h3>Buttons in Footer</h3>
    <Card
      header={{
        heading: "User Profile",
        subheading: "Update your personal information",
      }}
    >
      <Icon name="lucide:user" size={20} slot="heading-leading" />
      <Badge feedback="success" slot="header-trailing">
        <Icon name="lucide:circle-check" class={css({ w: "1em", h: "1em" })} />
        Active
      </Badge>
      <p class={css({ margin: "0" })}>
        Action buttons in the footer trailing slot follow a standard
        confirm/cancel pattern, keeping primary actions clearly separated from
        the card's content.
      </p>
      <div class={flex({ gap: "sm" })} slot="footer-trailing">
        <Button emphasis="secondary">Cancel</Button>
        <Button>Save</Button>
      </div>
    </Card>
  </div>
</div>

Custom Styling

The passThrough prop provides escape hatches for advanced customization: style accepts any Panda CSS SystemStyleObject applied to any slot, and props forwards arbitrary HTML attributes.

Custom Styled Card

Using passThrough for advanced customization

This card uses passThrough to apply custom styles to specific slots.

---
import Card from "../Card.astro";
import { stack } from "@pindoba/styled-system/patterns";
import { css } from "@pindoba/styled-system/css";
---

<div class={stack({ gap: "md", direction: "column", width: "100%" })}>
  <Card
    feedback="primary"
    border="default"
    header={{
      heading: "Custom Styled Card",
      subheading: "Using passThrough for advanced customization",
    }}
    passThrough={{
      root: {
        style: { borderStyle: "dashed" },
      },
      heading: {
        style: { color: "primary.text" },
      },
    }}
  >
    <p class={css({ margin: "0" })}>
      This card uses passThrough to apply custom styles to specific slots.
    </p>
  </Card>
</div>

The Card primitive composes with Avatar, Badge, Button, and Input to express a wide range of patterns. The demos below cover the most common compositions; everything else is a recombination of the same slots.

Message

Avatar in header.leading, timestamp as eyebrow, action buttons in header.trailing, emoji reactions and reply control in the footer.

Helena Rocha

Build #2,847 passed

Helena Rocha

@helena · 2h ago

Bundle shaved 4.2 KB after migrating the icon set to ESM, all 312 tests green, and Lighthouse held at 98 across the board. Pulling staging tomorrow morning — anyone want to pair on the rollout plan over coffee?

---
import Card from "../Card.astro";
import Avatar from "@pindoba/astro-avatar";
import Stamp from "@pindoba/astro-stamp";
import Button from "@pindoba/astro-button";
import { Icon } from "astro-icon/components";
import { stack, flex } from "@pindoba/styled-system/patterns";
import { css } from "@pindoba/styled-system/css";
---

<div class={stack({ gap: "lg", direction: "column", width: "100%" })}>
  <Card
    border="default"
    shadow="sm"
    header={{
      heading: "Helena Rocha",
      subheading: "@helena · 2h ago",
      eyebrow: "Build #2,847 passed",
      passThrough: {
        trailing: { style: { alignItems: "flex-start" } },
      },
    }}
  >
    <Avatar
      slot="header-leading"
      name="Helena Rocha"
      src="https://i.pravatar.cc/96?img=32"
      size="lg"
    />
    <div slot="header-trailing" class={flex({ gap: "2xs", align: "center" })}>
      <Button emphasis="ghost" shape="circle" size="sm" feedback="primary">
        <Icon name="lucide:reply" />
      </Button>
      <Button emphasis="ghost" shape="circle" size="sm">
        <Icon name="lucide:archive" />
      </Button>
    </div>
    <p class={css({ margin: "0", color: "panel.text" })}>
      Bundle shaved 4.2 KB after migrating the icon set to ESM, all 312 tests
      green, and Lighthouse held at 98 across the board. Pulling staging
      tomorrow morning — anyone want to pair on the rollout plan over coffee?
    </p>
    <div
      slot="footer-leading"
      class={flex({ gap: "2xs", align: "center", flex: "1" })}
    >
      <Stamp size="sm" shape="circle" emphasis="secondary" feedback="primary">
        <Icon name="lucide:sparkles" />
      </Stamp>
      <Stamp size="sm" shape="circle" emphasis="secondary" feedback="success">
        <Icon name="lucide:rocket" />
      </Stamp>
      <Stamp size="sm" shape="circle" emphasis="secondary" feedback="warning">
        <Icon name="lucide:coffee" />
      </Stamp>
      <Stamp size="sm" shape="circle" emphasis="secondary" feedback="warning">
        <Icon name="lucide:star" />
      </Stamp>
      <Stamp size="sm" shape="circle" emphasis="secondary" feedback="danger">
        <Icon name="lucide:flame" />
      </Stamp>
    </div>
    <Button
      slot="footer-trailing"
      emphasis="secondary"
      shape="square"
      radius="inner"
      size="sm"
    >
      <Icon name="lucide:paperclip" />
    </Button>
  </Card>
</div>

Date display

Eyebrow + oversized heading via passThrough.heading.style, paired with a small profile row in the footer.

Today

Aug 14

Olivia Reis
Stand-up at 10am Olivia Reis

Tomorrow

Aug 15

Felipe Ramos
Design critique Felipe Ramos

Friday

Aug 18

Marina Cruz
Sprint demo Marina Cruz
---
import Card from "../Card.astro";
import Avatar from "@pindoba/astro-avatar";
import { css } from "@pindoba/styled-system/css";
import { flex } from "@pindoba/styled-system/patterns";

const dates = [
  {
    eyebrow: "Today",
    heading: "Aug 14",
    person: { name: "Olivia Reis", src: "https://i.pravatar.cc/96?img=23" },
    label: "Stand-up at 10am",
  },
  {
    eyebrow: "Tomorrow",
    heading: "Aug 15",
    person: { name: "Felipe Ramos", src: "https://i.pravatar.cc/96?img=15" },
    label: "Design critique",
  },
  {
    eyebrow: "Friday",
    heading: "Aug 18",
    person: { name: "Marina Cruz", src: "https://i.pravatar.cc/96?img=44" },
    label: "Sprint demo",
  },
];
---

<div
  class={css({
    display: "grid",
    gridTemplateColumns: "repeat(3, minmax(0, 1fr))",
    gap: "md",
    width: "100%",
  })}
>
  {
    dates.map((d) => (
      <Card
        border="default"
        shadow="sm"
        header={{
          eyebrow: d.eyebrow,
          heading: d.heading,
          passThrough: {
            heading: {
              style: {
                fontSize: "4xl",
                fontWeight: "bold",
                letterSpacing: "tight",
              },
            },
          },
        }}
      >
        <div class={flex({ gap: "xs", align: "center", marginTop: "lg" })}>
          <Avatar name={d.person.name} src={d.person.src} size="sm" />
          <div class={css({ display: "flex", flexDirection: "column" })}>
            <span
              class={css({
                fontSize: "sm",
                fontWeight: "semibold",
                color: "panel.text.bold",
                lineHeight: "tight",
              })}
            >
              {d.label}
            </span>
            <span
              class={css({
                fontSize: "xs",
                color: "panel.text.muted",
                lineHeight: "tight",
              })}
            >
              {d.person.name}
            </span>
          </div>
        </div>
      </Card>
    ))
  }
</div>

Members

Avatar cluster in header.leading, with a stacked layout achieved via passThrough.root.style.

Member 1 Member 2 Member 3 Member 4
42 active tokens 8 added this week

Spot which design tokens are pulling weight across the codebase.

Member 1 Member 2 Member 3
16 components 2 in beta

Building blocks ready to ship — plus the ones still cooking.

Member 1 Member 2 Member 3 Member 4
94 contributors +5 this month

Designers and engineers nudging Pindoba forward, one PR at a time.

---
import Card from "../Card.astro";
import Avatar from "@pindoba/astro-avatar";
import { grid } from "@pindoba/styled-system/patterns";
import { css } from "@pindoba/styled-system/css";

const groups = [
  {
    heading: "42 active tokens",
    subheading: "8 added this week",
    body: "Spot which design tokens are pulling weight across the codebase.",
    faces: [
      "https://i.pravatar.cc/96?img=30",
      "https://i.pravatar.cc/96?img=31",
      "https://i.pravatar.cc/96?img=32",
      "https://i.pravatar.cc/96?img=33",
    ],
  },
  {
    heading: "16 components",
    subheading: "2 in beta",
    body: "Building blocks ready to ship — plus the ones still cooking.",
    faces: [
      "https://i.pravatar.cc/96?img=40",
      "https://i.pravatar.cc/96?img=41",
      "https://i.pravatar.cc/96?img=42",
    ],
  },
  {
    heading: "94 contributors",
    subheading: "+5 this month",
    body: "Designers and engineers nudging Pindoba forward, one PR at a time.",
    faces: [
      "https://i.pravatar.cc/96?img=50",
      "https://i.pravatar.cc/96?img=51",
      "https://i.pravatar.cc/96?img=52",
      "https://i.pravatar.cc/96?img=53",
    ],
  },
];
---

<div
  class={css({
    display: "grid",
    gridTemplateColumns: "repeat(3, minmax(0, 1fr))",
    gap: "md",
    width: "100%",
  })}
>
  {
    groups.map((g) => (
      <Card
        border="default"
        shadow="sm"
        passThrough={{
          root: { style: { textAlign: "center" } },
          content: {
            style: {
              display: "flex",
              flexDirection: "column",
              alignItems: "center",
              gap: "sm",
            },
          },
        }}
      >
        <div class={grid({ columns: 2, gap: "2xs", width: "fit-content" })}>
          {g.faces.slice(0, 4).map((src, i) => (
            <Avatar name={`Member ${i + 1}`} src={src} size="sm" />
          ))}
        </div>
        <div
          class={css({
            display: "flex",
            flexDirection: "column",
            gap: "3xs",
            marginTop: "xs",
          })}
        >
          <span
            class={css({
              fontSize: "lg",
              fontWeight: "semibold",
              color: "panel.text.bold",
              lineHeight: "tight",
            })}
          >
            {g.heading}
          </span>
          <span
            class={css({
              fontSize: "sm",
              color: "panel.text.muted",
              lineHeight: "tight",
            })}
          >
            {g.subheading}
          </span>
        </div>
        <p
          class={css({
            margin: "0",
            fontSize: "sm",
            color: "panel.text.muted",
          })}
        >
          {g.body}
        </p>
      </Card>
    ))
  }
</div>

Contact list

Header with avatar preview and counter, scrollable list in content via maxHeight + overflowY, primary action pinned in footer.

Pinned 1 Pinned 2 Pinned 3 +38

Invite to project

Workspace members 42 active
Helena Rocha

Helena Rocha

helena@pindoba.dev

Felipe Ramos

Felipe Ramos

felipe@pindoba.dev

Marina Cruz

Marina Cruz

marina@pindoba.dev

Olivia Reis

Olivia Reis

olivia@pindoba.dev

Bruno Carvalho

Bruno Carvalho

bruno@pindoba.dev

Camila Andrade

Camila Andrade

camila@pindoba.dev

Lucas Mendes

Lucas Mendes

lucas@pindoba.dev

Beatriz Lima

Beatriz Lima

beatriz@pindoba.dev

Rafael Costa

Rafael Costa

rafael@pindoba.dev

Sofia Almeida

Sofia Almeida

sofia@pindoba.dev

---
import Card from "../Card.astro";
import Banner from "@pindoba/astro-banner";
import Avatar from "@pindoba/astro-avatar";
import Group from "@pindoba/astro-group";
import Button from "@pindoba/astro-button";
import { Icon } from "astro-icon/components";
import { stack, flex } from "@pindoba/styled-system/patterns";
import { css } from "@pindoba/styled-system/css";

const members = [
  {
    name: "Helena Rocha",
    email: "helena@pindoba.dev",
    src: "https://i.pravatar.cc/96?img=32",
  },
  {
    name: "Felipe Ramos",
    email: "felipe@pindoba.dev",
    src: "https://i.pravatar.cc/96?img=15",
  },
  {
    name: "Marina Cruz",
    email: "marina@pindoba.dev",
    src: "https://i.pravatar.cc/96?img=44",
  },
  {
    name: "Olivia Reis",
    email: "olivia@pindoba.dev",
    src: "https://i.pravatar.cc/96?img=23",
  },
  {
    name: "Bruno Carvalho",
    email: "bruno@pindoba.dev",
    src: "https://i.pravatar.cc/96?img=12",
  },
  {
    name: "Camila Andrade",
    email: "camila@pindoba.dev",
    src: "https://i.pravatar.cc/96?img=47",
  },
  {
    name: "Lucas Mendes",
    email: "lucas@pindoba.dev",
    src: "https://i.pravatar.cc/96?img=8",
  },
  {
    name: "Beatriz Lima",
    email: "beatriz@pindoba.dev",
    src: "https://i.pravatar.cc/96?img=49",
  },
  {
    name: "Rafael Costa",
    email: "rafael@pindoba.dev",
    src: "https://i.pravatar.cc/96?img=11",
  },
  {
    name: "Sofia Almeida",
    email: "sofia@pindoba.dev",
    src: "https://i.pravatar.cc/96?img=21",
  },
];
---

<div class={stack({ gap: "lg", direction: "column", width: "100%" })}>
  <Card border="default" shadow="sm" header={{ heading: "Invite to project" }}>
    <Group slot="header-leading" variant="stack">
      <Avatar name="Pinned 1" src="https://i.pravatar.cc/96?img=60" size="sm" />
      <Avatar name="Pinned 2" src="https://i.pravatar.cc/96?img=61" size="sm" />
      <Avatar name="Pinned 3" src="https://i.pravatar.cc/96?img=62" size="sm" />
      <Avatar size="sm" name="+38" />
    </Group>
    <Button slot="header-trailing" emphasis="secondary" size="sm">
      Add member
      <Icon name="lucide:user-plus" />
    </Button>

    <div class={flex({ justify: "space-between", align: "center" })}>
      <span
        class={css({
          fontSize: "sm",
          fontWeight: "semibold",
          color: "panel.text.bold",
        })}>Workspace members</span
      >
      <span
        class={css({
          fontSize: "xs",
          color: "panel.text.muted",
          fontWeight: "medium",
        })}>42 active</span
      >
    </div>

    <div
      class={css({
        display: "flex",
        flexDirection: "column",
        gap: "sm",
        marginTop: "sm",
        maxHeight: "320px",
        overflowY: "auto",
        paddingRight: "2xs",
      })}
    >
      {
        members.map((m) => (
          <Banner size="sm" heading={m.name} subheading={m.email}>
            <Avatar slot="leading" name={m.name} src={m.src} size="md" />
            <Button
              slot="trailing"
              emphasis="secondary"
              shape="circle"
              size="sm"
            >
              <Icon name="lucide:plus" />
            </Button>
          </Banner>
        ))
      }
    </div>

    <Button
      slot="footer-trailing"
      feedback="primary"
      emphasis="primary"
      size="sm"
    >
      <Icon name="lucide:mail" />
      Send invites
    </Button>
  </Card>
</div>

Brand

A minimal card with no header or footer — content is centered via passThrough.content.style. interactive enables hover feedback.

Folha

Coqueiro

Raiz

---
import Card from "../Card.astro";
import { Icon } from "astro-icon/components";
import { css } from "@pindoba/styled-system/css";

const brands = [
  { name: "Folha", icon: "lucide:leaf" },
  { name: "Coqueiro", icon: "lucide:palmtree" },
  { name: "Raiz", icon: "lucide:sprout" },
];
---

<div
  class={css({
    display: "grid",
    gridTemplateColumns: "repeat(3, minmax(0, 1fr))",
    gap: "md",
    width: "100%",
  })}
>
  {
    brands.map((b) => (
      <Card
        interactive
        border="default"
        shadow="sm"
        passThrough={{
          content: {
            style: {
              minHeight: "240px",
              display: "flex",
              flexDirection: "column",
              alignItems: "center",
              justifyContent: "center",
              gap: "lg",
            },
          },
        }}
      >
        <div
          class={css({
            display: "flex",
            alignItems: "center",
            justifyContent: "center",
            color: "panel.text.bold",
          })}
        >
          <Icon name={b.icon} size={48} />
        </div>
        <p
          class={css({
            margin: "0",
            fontSize: "2xl",
            fontWeight: "bold",
            letterSpacing: "tight",
            color: "panel.text.bold",
          })}
        >
          {b.name}
        </p>
      </Card>
    ))
  }
</div>

Slot surfaces

Header and footer are both panel-backed, so each can carry its own background, feedback, and emphasis independent of the card body. Use it for stepped meta rows, filled hero strips, footer action zones, or success-tinted confirmation banners.

Helena Rocha

Helena Rocha

Pinned this morning

A subtly stepped header sets the meta block apart from the body without shouting — useful for activity feeds and threaded conversations.

Welcome aboard

Your workspace is set up and ready to roll.

Filled feedback surfaces work as hero strips — the header carries its own palette while the body stays in the card's neutral context.

Daily digest

284 events processed

The footer is panel-backed too — a stepped strip is a quiet way to mark the action zone or summary footer without a hard divider line.

Payment received

Order #4,128

A feedback-tinted footer doubles as a confirmation strip — success here, but warning and danger work the same way for nudges and alerts.

---
import Card from "../Card.astro";
import Avatar from "@pindoba/astro-avatar";
import Button from "@pindoba/astro-button";
import Stamp from "@pindoba/astro-stamp";
import { Icon } from "astro-icon/components";
import { css } from "@pindoba/styled-system/css";
import { flex } from "@pindoba/styled-system/patterns";
---

<div
  class={css({
    display: "grid",
    gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
    gap: "md",
    width: "100%",
  })}
>
  <Card
    border="default"
    shadow="sm"
    header={{
      heading: "Helena Rocha",
      subheading: "Pinned this morning",
      background: "surface.step.2",
    }}
  >
    <Avatar
      slot="header-leading"
      name="Helena Rocha"
      src="https://i.pravatar.cc/96?img=32"
      size="md"
    />
    <Button slot="header-trailing" emphasis="ghost" shape="circle" size="sm">
      <Icon name="lucide:ellipsis" />
    </Button>
    <p class={css({ margin: "0", color: "panel.text" })}>
      A subtly stepped header sets the meta block apart from the body without
      shouting — useful for activity feeds and threaded conversations.
    </p>
  </Card>

  <Card
    border="default"
    shadow="sm"
    header={{
      heading: "Welcome aboard",
      subheading: "Your workspace is set up and ready to roll.",
      feedback: "primary",
      emphasis: "primary",
      background: "surface.soft",
    }}
  >
    <Stamp
      slot="header-leading"
      size="md"
      shape="square"
      feedback="primary"
      emphasis="primary"
      background="surface.deep"
    >
      <Icon name="lucide:rocket" />
    </Stamp>
    <p class={css({ margin: "0", color: "panel.text" })}>
      Filled feedback surfaces work as hero strips — the header carries its own
      palette while the body stays in the card's neutral context.
    </p>
    <Button
      slot="footer-trailing"
      feedback="primary"
      emphasis="primary"
      size="sm"
    >
      Get started
      <Icon name="lucide:arrow-right" />
    </Button>
  </Card>

  <Card
    border="default"
    shadow="sm"
    header={{
      heading: "Daily digest",
      subheading: "284 events processed",
    }}
    footer={{
      background: "surface.step.2",
    }}
  >
    <p class={css({ margin: "0", color: "panel.text" })}>
      The footer is panel-backed too — a stepped strip is a quiet way to mark
      the action zone or summary footer without a hard divider line.
    </p>
    <div slot="footer-trailing" class={flex({ gap: "xs", align: "center" })}>
      <Button emphasis="ghost" size="sm">Skip</Button>
      <Button emphasis="primary" feedback="primary" size="sm">
        Open report
      </Button>
    </div>
  </Card>

  <Card
    border="default"
    shadow="sm"
    header={{
      heading: "Payment received",
      subheading: "Order #4,128",
    }}
    footer={{
      feedback: "success",
      background: "surface.soft",
    }}
  >
    <Stamp slot="header-leading" size="md" shape="circle" feedback="success">
      <Icon name="lucide:check" />
    </Stamp>
    <p class={css({ margin: "0", color: "panel.text" })}>
      A feedback-tinted footer doubles as a confirmation strip — success here,
      but warning and danger work the same way for nudges and alerts.
    </p>
    <Button
      slot="footer-trailing"
      emphasis="ghost"
      feedback="success"
      size="sm"
    >
      View receipt
      <Icon name="lucide:arrow-right" />
    </Button>
  </Card>
</div>

Eyebrow

The card header supports an eyebrow — a small kicker line above the heading. It is delegated to the underlying Banner component, so it inherits panel-aware text color and auto-flips to contrast under emphasis="primary". Pass it as a string or as { content, leading, trailing } to add an icon.

<Card header={{
  eyebrow: "Wednesday",
  heading: "Dec 14",
}} />

Props

props · 13 total
prop type default req description
size "sm""md""lg" "md" Controls padding for header, content, and footer sections, and the heading font size. sm: compact. md: default. lg: generous.
background "surface.soft""surface.step.1""surface.step.2""surface.step.3""surface.deep""transparent" "surface.soft" Surface background style forwarded to Panel. surface.soft is the lightest, surface.deep is the darkest. transparent removes the background entirely.
feedback "neutral""primary""success""warning""danger" "neutral" Semantic color palette forwarded to Panel.
border "none""default""bold""muted" "none" Box-shadow ring border forwarded to Panel.
radius "none""5xs""4xs""3xs""2xs""xs""sm""md""lg""xl""2xl""3xl""4xl""5xl""6xl""7xl""8xl""9xl""10xl""11xl""full" "md" Border radius of the card, forwarded to Panel.
padding "none""5xs""4xs""3xs""2xs""xs""sm""md""lg""xl""2xl" "none" Outer padding of the card, forwarded to Panel. Note: header, content, and footer have their own internal padding controlled by size.
interactive boolean false Apply pointer cursor and hover/active states.
emphasis "primary""secondary""tertiary" "secondary" Visual weight inherited from Panel. "primary" fills with the feedback 500 shade; "secondary" (default) uses surface tints; "tertiary" uses a neutral surface with feedback border and text.
translucent boolean false Apply a frosted glass effect with backdrop blur.
header PrimitiveCardHeaderProps undefined Configuration object for the card header section.
footer PrimitiveCardFooterProps undefined Configuration object for the card footer section.
passThrough { root?: { style?: SystemStyleObject; props?: HTMLAttributes<HTMLDivElement> }; header?: { style?: SystemStyleObject }; content?: { style?: SystemStyleObject }; footer?: { style?: SystemStyleObject }; heading?: { style?: SystemStyleObject }; subheading?: { style?: SystemStyleObject }; ... } undefined Custom styling and props for card slots. Each slot accepts style (SystemStyleObject) and props (HTML attributes).
...rest HTMLAttributes<HTMLDivElement> - Standard HTML div attributes

Header Props (PrimitiveCardHeaderProps)

props · 6 total
prop type default req description
eyebrow stringTitleWithLeadingTrailing undefined Small kicker line above the heading. Inherits panel-aware text color and auto-flips under emphasis="primary".
heading stringTitleWithLeadingTrailing undefined Main heading content. Can be a string, or an object with content, leading, and trailing.
subheading stringTitleWithLeadingTrailing undefined Subheading content below the main heading.
leading stringSnippet undefined Content displayed at the start of the header (e.g., an icon).
trailing stringSnippet undefined Content displayed at the end of the header.
passThrough object undefined Custom styling and props for header slots.

The footer is Panel-backed — it accepts the full panel surface API so you can give it its own background strip, divider, or interactive treatment independent of the card body. By default the footer is transparent and inherits the card’s feedback / emphasis via context.

props · 9 total
prop type default req description
leading stringSnippet undefined Content displayed at the start of the footer.
trailing stringSnippet undefined Content displayed at the end of the footer.
children Snippet undefined Main footer content.
background "surface.soft""surface.step.1""surface.step.2""surface.step.3""surface.deep""transparent" "transparent" Footer surface background. Use to set a distinct footer strip color.
feedback "inherit""neutral""primary""success""warning""danger" card's feedback Footer color palette. Inherits the card's feedback via context if unset.
emphasis "primary""secondary""tertiary" card's emphasis Footer panel emphasis. Inherits the card's emphasis via context if unset.
border "none""default""bold""muted""accent" "none" Footer box-shadow border.
interactive boolean false Apply pointer cursor and hover/active states on the footer.
passThrough object undefined Custom styling and props for footer slots.

TitleWithLeadingTrailing Interface

props · 3 total
prop type default req description
content stringSnippet - The main content of the title.
leading stringSnippet undefined Content before the title.
trailing stringSnippet undefined Content after the title.