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

Changelog · v2.4

Design tokens, refreshed

June 3, 2026 · 4 min read

New

Every color, space, and type value now flows from a single source of truth — tweak one token and the change ripples through every component, in every framework, automatically. This release also ships fluid headings and the new numeric text styles.

Helena Rocha
Helena RochaDesign Systems
---
import Card from "../Card.astro";
import Avatar from "@pindoba/astro-avatar";
import Badge from "@pindoba/astro-badge";
import Button from "@pindoba/astro-button";
import Stamp from "@pindoba/astro-stamp";
import { ArrowRight, Sparkles } from "@lucide/astro";
import { css } from "@pindoba/styled-system/css";
import { flex } from "@pindoba/styled-system/patterns";
---

<div class={css({ width: "100%", maxWidth: "42rem" })}>
  <Card
    border="default"
    shadow="sm"
    radius="lg"
    header={{
      eyebrow: "Changelog · v2.4",
      heading: "Design tokens, refreshed",
      subheading: "June 3, 2026 · 4 min read",
      layout: {
        leading: { span: "below" },
        trailing: { align: "start" },
      },
    }}
  >
    <Stamp
      slot="header-leading"
      size="md"
      shape="square"
      feedback="primary"
      emphasis="primary"
    >
      <Sparkles />
    </Stamp>
    <Badge slot="header-trailing" feedback="success">New</Badge>

    <p class={css({ margin: "0", color: "panel.text" })}>
      Every color, space, and type value now flows from a single source of truth
      — tweak one token and the change ripples through every component, in every
      framework, automatically. This release also ships fluid headings and the
      new numeric text styles.
    </p>

    <div slot="footer-leading" class={flex({ gap: "xs", align: "center" })}>
      <Avatar
        name="Helena Rocha"
        src="https://i.pravatar.cc/96?img=32"
        size="sm"
      />
      <div class={css({ display: "flex", flexDirection: "column" })}>
        <span
          class={css({
            fontSize: "sm",
            fontWeight: "semibold",
            color: "panel.text.bold",
            lineHeight: "tight",
          })}>Helena Rocha</span
        >
        <span
          class={css({
            fontSize: "xs",
            color: "panel.text.muted",
            lineHeight: "tight",
          })}>Design Systems</span
        >
      </div>
    </div>
    <Button slot="footer-trailing" size="sm">
      Read more
      <ArrowRight />
    </Button>
  </Card>
</div>

Feedback

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

Weekly digest is ready

12 updates since Monday

A neutral card sets the calm default — quiet enough for routine notifications that don't demand attention.

Dark mode has landed

Now available in Settings

Primary leans on the brand color to spotlight announcements, new features, and the action you'd most like someone to take.

Payment received

Invoice #4,128 · R$ 420.00

Success confirms something went right — a completed payment, a passing build, a saved change.

Storage almost full

8.7 GB of 10 GB used

Warning flags something that needs attention soon but isn't broken yet — a soft nudge before it becomes a problem.

Subscription expired

Renew to restore access

Danger marks the highest-stakes states — failures, expirations, and destructive actions that can't be undone.

---
import Card from "../Card.astro";
import Button from "@pindoba/astro-button";
import Stamp from "@pindoba/astro-stamp";
import {
  Bell,
  CircleCheck,
  Megaphone,
  OctagonAlert,
  TriangleAlert,
} from "@lucide/astro";
import { stack } from "@pindoba/styled-system/patterns";
import { css } from "@pindoba/styled-system/css";

const items = [
  {
    feedback: "neutral",
    icon: Bell,
    heading: "Weekly digest is ready",
    subheading: "12 updates since Monday",
    body: "A neutral card sets the calm default — quiet enough for routine notifications that don't demand attention.",
    cta: "View all",
  },
  {
    feedback: "primary",
    icon: Megaphone,
    heading: "Dark mode has landed",
    subheading: "Now available in Settings",
    body: "Primary leans on the brand color to spotlight announcements, new features, and the action you'd most like someone to take.",
    cta: "Try it now",
  },
  {
    feedback: "success",
    icon: CircleCheck,
    heading: "Payment received",
    subheading: "Invoice #4,128 · R$ 420.00",
    body: "Success confirms something went right — a completed payment, a passing build, a saved change.",
    cta: "View receipt",
  },
  {
    feedback: "warning",
    icon: TriangleAlert,
    heading: "Storage almost full",
    subheading: "8.7 GB of 10 GB used",
    body: "Warning flags something that needs attention soon but isn't broken yet — a soft nudge before it becomes a problem.",
    cta: "Upgrade plan",
  },
  {
    feedback: "danger",
    icon: OctagonAlert,
    heading: "Subscription expired",
    subheading: "Renew to restore access",
    body: "Danger marks the highest-stakes states — failures, expirations, and destructive actions that can't be undone.",
    cta: "Renew now",
  },
] as const;
---

<div class={stack({ gap: "md", direction: "column", width: "100%" })}>
  {
    items.map((item) => {
      const LeadingIcon = item.icon;
      return (
        <Card
          feedback={item.feedback}
          border="default"
          header={{
            heading: item.heading,
            subheading: item.subheading,
          }}
        >
          <Stamp
            slot="header-leading"
            size="md"
            shape="square"
            feedback={item.feedback}
            emphasis="secondary"
          >
            <LeadingIcon />
          </Stamp>
          <p class={css({ margin: "0", color: "panel.text" })}>{item.body}</p>
          <Button slot="footer-trailing" size="sm" feedback={item.feedback}>
            {item.cta}
          </Button>
        </Card>
      );
    })
  }
</div>

Size

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

2X-Small

Deploy to production

staging → main · 14 commits

Queued

Roll out the latest build to every region. Approvals are in and the canary has stayed healthy for 20 minutes.

Queued 2 min ago
X-Small

Deploy to production

staging → main · 14 commits

Queued

Roll out the latest build to every region. Approvals are in and the canary has stayed healthy for 20 minutes.

Queued 2 min ago
Small

Deploy to production

staging → main · 14 commits

Queued

Roll out the latest build to every region. Approvals are in and the canary has stayed healthy for 20 minutes.

Queued 2 min ago
Medium · default

Deploy to production

staging → main · 14 commits

Queued

Roll out the latest build to every region. Approvals are in and the canary has stayed healthy for 20 minutes.

Queued 2 min ago
Large

Deploy to production

staging → main · 14 commits

Queued

Roll out the latest build to every region. Approvals are in and the canary has stayed healthy for 20 minutes.

Queued 2 min ago
---
import Card from "../Card.astro";
import Badge from "@pindoba/astro-badge";
import Button from "@pindoba/astro-button";
import Stamp from "@pindoba/astro-stamp";
import { Rocket } from "@lucide/astro";
import { stack, flex } from "@pindoba/styled-system/patterns";
import { css } from "@pindoba/styled-system/css";

const sizes = [
  { size: "2xs", label: "2X-Small" },
  { size: "xs", label: "X-Small" },
  { size: "sm", label: "Small" },
  { size: "md", label: "Medium · default" },
  { size: "lg", label: "Large" },
] as const;
---

<div class={stack({ gap: "xl", direction: "column", width: "100%" })}>
  {
    sizes.map((s) => (
      <div>
        <span
          class={css({
            textStyle: "label.sm",
            color: "panel.text.muted",
            display: "block",
            marginBottom: "xs",
          })}
        >
          {s.label}
        </span>
        <Card
          size={s.size}
          border="default"
          shadow="sm"
          header={{
            heading: "Deploy to production",
            subheading: "staging → main · 14 commits",
          }}
        >
          <Stamp
            slot="header-leading"
            size="md"
            shape="square"
            feedback="primary"
            emphasis="primary"
          >
            <Rocket />
          </Stamp>
          <Badge slot="header-trailing" feedback="warning">
            Queued
          </Badge>
          <p class={css({ margin: "0", color: "panel.text" })}>
            Roll out the latest build to every region. Approvals are in and the
            canary has stayed healthy for 20 minutes.
          </p>
          <span
            slot="footer-leading"
            class={css({ fontSize: "xs", color: "panel.text.muted" })}
          >
            Queued 2 min ago
          </span>
          <div
            slot="footer-trailing"
            class={flex({ gap: "sm", align: "center" })}
          >
            <Button emphasis="secondary" size="sm">
              Cancel
            </Button>
            <Button feedback="primary" size="sm">
              Deploy
            </Button>
          </div>
        </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 the header

Q3 Roadmap.pdf

Edited 2 hours ago by Felipe

Draft

Outlines the quarter's milestones, owners, and target ship dates across the platform and design-system tracks.

Ghost button in the header

Project settings

Visibility, access, and integrations

Beta

Control who can view and edit this project, and connect the tools your team already uses every day.

Actions in the footer

Helena Rocha

Product designer · São Paulo

Active

Update the personal details your teammates see across the workspace.

---
import Card from "../Card.astro";
import Button from "@pindoba/astro-button";
import Badge from "@pindoba/astro-badge";
import {
  CircleCheck,
  Clock,
  Ellipsis,
  FileText,
  FlaskConical,
  Pencil,
  Settings,
  User,
} from "@lucide/astro";
import { stack, flex } from "@pindoba/styled-system/patterns";
import { css } from "@pindoba/styled-system/css";

const labelClass = css({
  textStyle: "label.sm",
  color: "panel.text.muted",
  display: "block",
  marginBottom: "xs",
});

// Top-align the header trailing slot (badges / buttons) against the
// multi-line heading + subheading block.
const topTrailing = { layout: { trailing: { align: "start" } } } as const;
---

<div class={stack({ gap: "xl", direction: "column" })}>
  <div>
    <span class={labelClass}>Icon in the header</span>
    <Card
      header={{
        heading: "Q3 Roadmap.pdf",
        subheading: "Edited 2 hours ago by Felipe",
        ...topTrailing,
      }}
    >
      <FileText size={20} slot="heading-leading" />
      <Clock size={14} slot="subheading-leading" />
      <Badge feedback="warning" slot="header-trailing">
        <Pencil class={css({ w: "1em", h: "1em" })} />
        Draft
      </Badge>
      <p class={css({ margin: "0", color: "panel.text" })}>
        Outlines the quarter's milestones, owners, and target ship dates across
        the platform and design-system tracks.
      </p>
    </Card>
  </div>

  <div>
    <span class={labelClass}>Ghost button in the header</span>
    <Card
      header={{
        heading: "Project settings",
        subheading: "Visibility, access, and integrations",
        ...topTrailing,
      }}
    >
      <Settings size={20} slot="header-leading" />
      <div class={flex({ gap: "sm", align: "center" })} slot="header-trailing">
        <Badge feedback="primary" emphasis="secondary">
          <FlaskConical class={css({ w: "1em", h: "1em" })} />
          Beta
        </Badge>
        <Button emphasis="ghost" shape="circle">
          <Ellipsis />
        </Button>
      </div>
      <p class={css({ margin: "0", color: "panel.text" })}>
        Control who can view and edit this project, and connect the tools your
        team already uses every day.
      </p>
    </Card>
  </div>

  <div>
    <span class={labelClass}>Actions in the footer</span>
    <Card
      header={{
        heading: "Helena Rocha",
        subheading: "Product designer · São Paulo",
        ...topTrailing,
      }}
    >
      <User size={20} slot="heading-leading" />
      <Badge feedback="success" slot="header-trailing">
        <CircleCheck class={css({ w: "1em", h: "1em" })} />
        Active
      </Badge>
      <p class={css({ margin: "0", color: "panel.text" })}>
        Update the personal details your teammates see across the workspace.
      </p>
      <div class={flex({ gap: "sm" })} slot="footer-trailing">
        <Button emphasis="secondary">Cancel</Button>
        <Button>Save changes</Button>
      </div>
    </Card>
  </div>
</div>

When the header has a trailing element, the card pins it to the top-right by default (layout.trailing.align: "start"), so contextual actions align with the first line of a multi-line heading instead of centering. Override it per card via header={{ layout: { trailing: { align: "center" } } }}, and use header={{ layout: { trailing: { justify: "end" } } }} to flush a fixed-width trailing region (like a metadata path) to the right edge.

States

selected renders a dashed border to mark the active card (visible when the card also has a border), and muted dims and desaturates a locked or unavailable card. Both are purely visual — no more reaching for passThrough.root.style with borderStyle / opacity / filter.

Default

Main branch

The card in its resting state.

Selected

Main branch

selected renders a dashed border to mark the active card.

Muted

Locked branch

muted dims and desaturates a locked or unavailable card.

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

const labelClass = css({
  textStyle: "label.sm",
  color: "panel.text.muted",
  display: "block",
  marginBottom: "xs",
});
---

<div class={grid({ columns: { base: 1, sm: 3 }, gap: "lg" })}>
  <div>
    <span class={labelClass}>Default</span>
    <Card border="default" header={{ heading: "Main branch" }}>
      <p class={css({ margin: "0", color: "panel.text" })}>
        The card in its resting state.
      </p>
    </Card>
  </div>

  <div>
    <span class={labelClass}>Selected</span>
    <Card selected border="default" header={{ heading: "Main branch" }}>
      <p class={css({ margin: "0", color: "panel.text" })}>
        <code>selected</code> renders a dashed border to mark the active card.
      </p>
    </Card>
  </div>

  <div>
    <span class={labelClass}>Muted</span>
    <Card muted border="default" header={{ heading: "Locked branch" }}>
      <p class={css({ margin: "0", color: "panel.text" })}>
        <code>muted</code> dims and desaturates a locked or unavailable card.
      </p>
    </Card>
  </div>
</div>

Runtime surface updates

In Svelte, React, and Vue the card’s surface follows its props — flip emphasis or feedback in state and it repaints. Astro renders once on the server, so the same card gets a runtime path instead: render it with the reactive prop (which serializes the surface config into the HTML) and call updateCard() from @pindoba/astro-card/runtime:

import { updateCard } from "@pindoba/astro-card/runtime";

updateCard(cardElement, { emphasis: "primary", feedback: "danger" });

The patch re-runs the card’s connect in the browser and swaps the surface classes on the root and on the header/footer parts that inherit from the card — parts with an explicit surface of their own stay put, exactly like a re-render. Only the surface axes (emphasis, feedback, background, translucent, border, shadow, interactive) are patchable; layout never moves. The same functions work from vanilla JavaScript via @pindoba/core-card.

Emphasis
Feedback

Runtime surface

No re-render, no framework runtime

This card was server-rendered by Astro. The buttons above call updateCard() from @pindoba/astro-card/runtime, which re-runs the card's connect in the browser and swaps the surface classes on the root, header, and footer — the same visual change a prop flip produces in the reactive frameworks.

updateCard(el, { emphasis })
---
import Card from "../Card.astro";
import Button from "@pindoba/astro-button";
import { flex, stack } from "@pindoba/styled-system/patterns";
import { css } from "@pindoba/styled-system/css";

const emphases = ["secondary", "primary", "tertiary"] as const;
const feedbacks = [
  "neutral",
  "primary",
  "success",
  "warning",
  "danger",
] as const;

const labelClass = css({
  textStyle: "label.sm",
  color: "panel.text.muted",
  display: "block",
  marginBottom: "xs",
});
---

<div class={stack({ gap: "md" })} data-card-reactive-demo>
  <div class={stack({ gap: "sm" })}>
    <div>
      <span class={labelClass}>Emphasis</span>
      <div class={flex({ gap: "sm", wrap: "wrap" })}>
        {
          emphases.map((emphasis) => (
            <Button size="sm" emphasis="secondary" data-set-emphasis={emphasis}>
              {emphasis}
            </Button>
          ))
        }
      </div>
    </div>
    <div>
      <span class={labelClass}>Feedback</span>
      <div class={flex({ gap: "sm", wrap: "wrap" })}>
        {
          feedbacks.map((feedback) => (
            <Button size="sm" emphasis="secondary" data-set-feedback={feedback}>
              {feedback}
            </Button>
          ))
        }
      </div>
    </div>
  </div>

  <Card
    reactive
    border="default"
    header={{
      heading: "Runtime surface",
      subheading: "No re-render, no framework runtime",
    }}
    footer={{ trailing: "updateCard(el, { emphasis })" }}
  >
    <p class={css({ margin: "0", color: "panel.text" })}>
      This card was server-rendered by Astro. The buttons above call{" "}
      <code>updateCard()</code> from <code>@pindoba/astro-card/runtime</code>,
      which re-runs the card's connect in the browser and swaps the surface
      classes on the root, header, and footer — the same visual change a prop
      flip produces in the reactive frameworks.
    </p>
  </Card>
</div>

<script>
  import { updateCard } from "@pindoba/astro-card/runtime";

  const EMPHASES = ["primary", "secondary", "tertiary"] as const;
  const FEEDBACKS = [
    "inherit",
    "neutral",
    "primary",
    "success",
    "warning",
    "danger",
  ] as const;

  function boot() {
    const roots = document.querySelectorAll("[data-card-reactive-demo]");
    for (const root of roots) {
      if (root.hasAttribute("data-card-reactive-demo-initialized")) continue;
      root.setAttribute("data-card-reactive-demo-initialized", "");

      root.addEventListener("click", (event) => {
        const target = event.target instanceof Element ? event.target : null;
        const card = root.querySelector("[data-component='card']");
        if (!card) return;

        const emphasisButton = target?.closest("[data-set-emphasis]");
        if (emphasisButton) {
          const value = emphasisButton.getAttribute("data-set-emphasis");
          const emphasis = EMPHASES.find((candidate) => candidate === value);
          if (emphasis) updateCard(card, { emphasis });
          return;
        }

        const feedbackButton = target?.closest("[data-set-feedback]");
        if (feedbackButton) {
          const value = feedbackButton.getAttribute("data-set-feedback");
          const feedback = FEEDBACKS.find((candidate) => candidate === value);
          if (feedback) updateCard(card, { feedback });
        }
      });
    }
  }

  boot();
  document.addEventListener("astro:page-load", boot);
</script>

Media

The media prop puts an image on the card — full-bleed, radius-aware, and framework-consistent. Give it a src/alt for the built-in <img> (lazy-loaded, object-fit: cover), or provide custom content (a <picture>, video, carousel) via the media slot (Astro/Vue) or media.children (Svelte/React) — the wrapper still handles bleed, clipping, and corners.

  • placementtop (default) and bottom are full-bleed blocks; start/end pin the media as a full-height column beside the body (set the column with width, default 33%); background covers the whole card behind the content and turns on a darkening scrim by default so text stays legible (opt out with scrim: false, or opt in on any placement).
  • aspectauto (intrinsic), square, video (16∶9), portrait (3∶4), or wide (21∶9); fixed ratios keep card grids aligned.
  • fitcover (default) or contain.
  • inset — renders the media inside the card padding with a corner radius concentric to the card’s own instead of bleeding to the edges (top/bottom placements).

The wrapper, image, and scrim are all addressable via passThrough.media / passThrough.mediaImg / passThrough.mediaScrim.

Sunlight through a forest canopy

Field notes

Under the canopy

A walk through the reserve

The media region bleeds to the card edges and inherits the card's top corners.

Night expedition

Placement “background”

Content sits on a darkening scrim so text stays readable over the photo.

A river bend seen from above

River survey

Placement “start”

The image spans the card's full height while the body flows beside it — no wrapper markup needed.

Coastline at dusk

Coastal preview

Inset media

New

inset keeps the image inside the card padding with a corner radius concentric to the card's own.

---
import Card from "../Card.astro";
import Badge from "@pindoba/astro-badge";
import Button from "@pindoba/astro-button";
import { css } from "@pindoba/styled-system/css";
import { grid } from "@pindoba/styled-system/patterns";
---

<div
  class={grid({
    columns: { base: 1, md: 2 },
    gap: "md",
    width: "100%",
    maxWidth: "52rem",
  })}
>
  {/* Cover image: full-bleed on top, fixed aspect so grids align */}
  <Card
    border="default"
    media={{
      src: "https://picsum.photos/seed/pindoba-forest/800/450",
      alt: "Sunlight through a forest canopy",
      aspect: "video",
    }}
    header={{
      eyebrow: "Field notes",
      heading: "Under the canopy",
      subheading: "A walk through the reserve",
    }}
  >
    <p class={css({ color: "panel.text", margin: "0" })}>
      The media region bleeds to the card edges and inherits the card's top
      corners.
    </p>
    <Button slot="footer-trailing" size="sm" emphasis="secondary">
      Read more
    </Button>
  </Card>

  {/* Background cover with the built-in legibility scrim */}
  <Card
    radius="lg"
    media={{
      src: "https://picsum.photos/seed/pindoba-dunes/800/500",
      alt: "",
      placement: "background",
    }}
    header={{
      heading: "Night expedition",
      subheading: "Placement “background”",
    }}
    passThrough={{ root: { style: css.raw({ minHeight: "14rem" }) } }}
  >
    <p class={css({ color: "panel.text", margin: "0" })}>
      Content sits on a darkening scrim so text stays readable over the photo.
    </p>
    <Button slot="footer-trailing" size="sm" feedback="primary">
      Book now
    </Button>
  </Card>

  {
    /* Horizontal: media pinned as a start column (full grid width so the
      card reads as a row, not a tall sliver) */
  }
  <Card
    border="default"
    media={{
      src: "https://picsum.photos/seed/pindoba-river/400/600",
      alt: "A river bend seen from above",
      placement: "start",
      width: "14rem",
    }}
    header={{ heading: "River survey", subheading: "Placement “start”" }}
    passThrough={{ root: { style: css.raw({ gridColumn: "1 / -1" }) } }}
  >
    <p class={css({ color: "panel.text", margin: "0" })}>
      The image spans the card's full height while the body flows beside it — no
      wrapper markup needed.
    </p>
  </Card>

  {/* Inset: media inside the padding, concentric corners */}
  <Card
    border="default"
    media={{
      src: "https://picsum.photos/seed/pindoba-coast/800/340",
      alt: "Coastline at dusk",
      aspect: "wide",
      inset: true,
    }}
    header={{ heading: "Coastal preview", subheading: "Inset media" }}
    passThrough={{ root: { style: css.raw({ gridColumn: "1 / -1" }) } }}
  >
    <Badge slot="header-trailing" feedback="success">New</Badge>
    <p class={css({ color: "panel.text", margin: "0" })}>
      <code>inset</code> keeps the image inside the card padding with a corner radius
      concentric to the card's own.
    </p>
  </Card>
</div>

Corner slot

The corner slot floats a control in the card’s top-right corner — a dismiss button, an overflow menu, a status pill — and works even on a headerless card, sitting over the content rather than inside a header row. It’s inset by the card’s own padding so it lands in the true padded corner. Pass it as the corner prop (React/Svelte/Vue) or the corner named slot (Astro/Vue).

The corner slot floats a control in the card's top-right corner — here a dismiss button — and works even with no header, sitting over the content instead of inside a header row.

---
import Card from "../Card.astro";
import Button from "@pindoba/astro-button";
import { X } from "@lucide/astro";
import { css } from "@pindoba/styled-system/css";
---

<div class={css({ width: "100%", maxWidth: "32rem" })}>
  <Card border="default" radius="lg">
    <Button
      slot="corner"
      emphasis="ghost"
      shape="circle"
      size="sm"
      aria-label="Dismiss"
    >
      <X size={16} />
    </Button>
    <p class={css({ margin: "0", color: "panel.text" })}>
      The <code>corner</code> slot floats a control in the card's top-right corner
      — here a dismiss button — and works even with no header, sitting over the content
      instead of inside a header row.
    </p>
  </Card>
</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.

Welcome

Your workspace is ready

A few things you can do right away

Invite teammates and assign roles
Import or generate your design tokens
Browse the component gallery
Takes about 5 minutes
---
import Card from "../Card.astro";
import Button from "@pindoba/astro-button";
import { ArrowRight, Check } from "@lucide/astro";
import { stack, flex } from "@pindoba/styled-system/patterns";
import { css } from "@pindoba/styled-system/css";

const steps = [
  "Invite teammates and assign roles",
  "Import or generate your design tokens",
  "Browse the component gallery",
];
---

<div class={css({ width: "100%", maxWidth: "38rem" })}>
  <Card
    feedback="primary"
    border="default"
    radius="lg"
    shadow="md"
    header={{
      eyebrow: "Welcome",
      heading: "Your workspace is ready",
      subheading: "A few things you can do right away",
    }}
    passThrough={{
      root: {
        style: {
          backgroundImage:
            "radial-gradient(130% 150% at 100% 0%, token(colors.primary.surface.ground), token(colors.primary.surface.peak))",
          borderColor: "primary.border.bold",
        },
      },
      heading: { style: { color: "primary.text.bold" } },
    }}
  >
    <div class={stack({ gap: "sm", direction: "column" })}>
      {
        steps.map((step) => (
          <div class={flex({ gap: "xs", align: "center" })}>
            <span
              class={flex({
                align: "center",
                justify: "center",
                color: "primary.text.accent",
              })}
            >
              <Check size={18} />
            </span>
            <span class={css({ color: "panel.text" })}>{step}</span>
          </div>
        ))
      }
    </div>

    <span
      slot="footer-leading"
      class={css({ color: "primary.text.muted", fontSize: "sm" })}
    >
      Takes about 5 minutes
    </span>
    <Button slot="footer-trailing" feedback="primary" size="sm">
      Take the tour
      <ArrowRight />
    </Button>
  </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. layout: { root: { align: "start" }, leading: { align: "center" } } pins the actions to the top while keeping the avatar centered on the text block.

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 {
  Archive,
  Coffee,
  Flame,
  Paperclip,
  Reply,
  Rocket,
  Sparkles,
  Star,
} from "@lucide/astro";
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",
      layout: {
        root: { align: "start" },
        leading: { align: "center" },
      },
    }}
  >
    <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">
        <Reply />
      </Button>
      <Button emphasis="ghost" shape="circle" size="sm">
        <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">
        <Sparkles />
      </Stamp>
      <Stamp size="sm" shape="circle" emphasis="secondary" feedback="success">
        <Rocket />
      </Stamp>
      <Stamp size="sm" shape="circle" emphasis="secondary" feedback="warning">
        <Coffee />
      </Stamp>
      <Stamp size="sm" shape="circle" emphasis="secondary" feedback="warning">
        <Star />
      </Stamp>
      <Stamp size="sm" shape="circle" emphasis="secondary" feedback="danger">
        <Flame />
      </Stamp>
    </div>
    <Button
      slot="footer-trailing"
      emphasis="secondary"
      shape="square"
      radius="inner"
      size="sm"
    >
      <Paperclip />
    </Button>
  </Card>
</div>

Date display

Eyebrow + oversized heading via headingTextStyle="numeric.lg", paired with a small profile row in the footer.

Today

Aug 14

Olivia Reis
Stand-up at 10amOlivia Reis

Tomorrow

Aug 15

Felipe Ramos
Design critiqueFelipe Ramos

Friday

Aug 18

Marina Cruz
Sprint demoMarina 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,
          headingTextStyle: "numeric.lg",
        }}
      >
        <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 on top, then a centered <Banner layout={{ content: { align: "center" } }}> inside the content carries the heading and subheading.

Member 1Member 2Member 3Member 4

42 active tokens

8 added this week

Spot which design tokens are pulling weight across the codebase.

Member 1Member 2Member 3

16 components

2 in beta

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

Member 1Member 2Member 3Member 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 Banner from "@pindoba/astro-banner";
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>
        <Banner
          layout={{ content: { align: "center" } }}
          heading={g.heading}
          headingTextStyle="heading.xs"
          subheading={g.subheading}
        />
        <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 1Pinned 2Pinned 3+38

Invite to project

Workspace members42 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 { Mail, Plus, UserPlus } from "@lucide/astro";
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
      <UserPlus />
    </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"
            >
              <Plus />
            </Button>
          </Banner>
        ))
      }
    </div>

    <Button
      slot="footer-trailing"
      feedback="primary"
      emphasis="primary"
      size="sm"
    >
      <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 Banner from "@pindoba/astro-banner";
import { Leaf, Sprout, TreePalm } from "@lucide/astro";
import { css } from "@pindoba/styled-system/css";

const brands = [
  { name: "Folha", icon: Leaf },
  { name: "Coqueiro", icon: TreePalm },
  { name: "Raiz", icon: Sprout },
];
---

<div
  class={css({
    display: "grid",
    gridTemplateColumns: "repeat(3, minmax(0, 1fr))",
    gap: "md",
    width: "100%",
  })}
>
  {
    brands.map((b) => {
      const BrandIcon = b.icon;
      return (
        <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",
            })}
          >
            <BrandIcon size={48} />
          </div>
          <Banner
            layout={{ content: { align: "center" } }}
            heading={b.name}
            headingTextStyle="heading.md"
          />
        </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 { ArrowRight, Check, Ellipsis, Rocket } from "@lucide/astro";
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.base",
    }}
  >
    <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">
      <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.peak",
    }}
  >
    <Stamp
      slot="header-leading"
      size="md"
      shape="square"
      feedback="primary"
      emphasis="primary"
      background="surface.ground"
    >
      <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
      <ArrowRight />
    </Button>
  </Card>

  <Card
    border="default"
    shadow="sm"
    header={{
      heading: "Daily digest",
      subheading: "284 events processed",
    }}
    footer={{
      background: "surface.base",
    }}
  >
    <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.peak",
    }}
  >
    <Stamp slot="header-leading" size="md" shape="circle" feedback="success">
      <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
      <ArrowRight />
    </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 · 32 shown · 32 total
active
boolean"current""checked""current-within"
default false

Mark this as the chosen one in a set (a current nav item, a checked option, a selected card). Where `interactive` says it *can* be clicked, `active` says it *is* the current choice. Use `activeEmphasis` to control how loud the state is. Combined with `interactive`, hover and press restart their stepping from the active baseline rather than falling back to the unchosen ramp. The three values differ only in what drives the state: `true` by the prop (via `data-panel-active`), `"checked"` by a wrapped native input (`:has(input:checked)` — zero JS, nothing to re-render), and `"current"` by an existing `aria-current`.

activeEmphasis
"primary""secondary""tertiary"
default the panel's `emphasis`

How loud the `active` state is. Defaults to the panel's own `emphasis`, so the chosen state lands on the same ramp as the resting look — this prop is the override for when the active item should be louder (or quieter) than the panel itself. `"primary"` fills with the feedback accent ramp and flips text to the contrast scale (~150 RGB units of separation — the option that reads at a glance in a dense list). `"secondary"` tints the feedback surface ramp and `"tertiary"` the neutral ramp; both promote text to `bold` and lift the border to the default line, but move the surface only ~7–18 units.

as
"header""footer""div""section""article""aside""main""nav""dialog""form""fieldset""ul""ol""li""a""button""label""span""kbd"
default "div"

Root element tag. Set `"label"` to make the whole card a control.

background
"surface.peak""surface.hill""surface.base""surface.valley""surface.ground""transparent"

Surface background from the Panel scale (`surface.peak` → `surface.ground`, or `transparent`).

border
"none""bold""default""muted""accent"

Border style: `none`, `default`, `bold`, or `muted`.

borderActive
"none""bold""inherit""default""muted""accent"

No description yet.

borderInteract
"none""bold""default""muted""accent"

Border style applied on hover / focus interaction.

children slot svelte
Snippet

No description yet.

corner slot svelte
Snippet

Absolutely-positioned top-right overlay; renders even without a header.

element binding svelte
HTMLElementnull

The card's root DOM element — bind with `bind:element`.

emphasis
"primary""secondary""tertiary"

Visual emphasis / prominence level.

feedback
"primary""neutral""success""warning""danger""inherit"

Semantic color tone: `neutral`, `primary`, `success`, `warning`, `danger`, or `inherit`.

footer svelte
PrimitiveCardFooterProps

No description yet.

header svelte
PrimitiveCardHeaderProps

No description yet.

interactive
boolean

Enable interactive (hover / focus / press) affordances and states.

media svelte
CardMediaConfig

Media region — an image (or custom media snippet via `children`) placed full-bleed at the top/bottom, as a start/end column, or as a background cover behind the content.

mediaPlacement
"background""bottom""top""end""start"

Layout hook for the media region — flips the root layout for `start`/ `end` columns and layers siblings for `background`. Set automatically by the high-level Card from `media.placement`; only set it manually when composing primitives.

mediaWidth
string
default "33%"

Column width of a `start`/`end` media placement (any CSS length or percentage). Set automatically by the high-level Card from `media.width`.

muted
boolean
default false

Muted state — dims and desaturates the whole card (locked / inactive / unavailable). Purely visual; pair with `aria-disabled`/`inert` for semantics.

padding
"sm""md""lg""xl""2xl""none""xs""3xl""4xl""5xl""6xl""7xl""8xl""4xs""3xs""2xs""9xl""10xl""11xl"

Inner padding from the spacing scale.

passThrough
CardPassThrough<RootElementAttributes>

Per-slot style and attribute override bag. Each slot accepts `style` (a Panda CSS `SystemStyleObject`) and `props` (extra HTML attributes) merged onto the slot's element.

radius
"sm""md""lg""xl""2xl""none""xs""3xl""4xl""5xl""6xl""full""2xs""inner""inherit"

Corner radius override from the spacing scale (or `full` for fully rounded).

radiusBottom
"sm""md""lg""xl""2xl""none""xs""3xl""4xl""5xl""6xl""full""2xs""inner""inherit"

Corner radius for the bottom-left and bottom-right corners.

radiusLeft
"sm""md""lg""xl""2xl""none""xs""3xl""4xl""5xl""6xl""full""2xs""inner""inherit"

Corner radius for the top-left and bottom-left corners.

radiusRight
"sm""md""lg""xl""2xl""none""xs""3xl""4xl""5xl""6xl""full""2xs""inner""inherit"

Corner radius for the top-right and bottom-right corners.

radiusTop
"sm""md""lg""xl""2xl""none""xs""3xl""4xl""5xl""6xl""full""2xs""inner""inherit"

Corner radius for the top-left and top-right corners.

reactive
boolean
default false

Serialize the card's surface config into the rendered HTML so `updateCard()` can change `emphasis`/`feedback`/`background` (and the other surface axes) at runtime. Meant for Astro / vanilla JS, where props don't re-render; reactive frameworks don't need it.

reserveBorderSpace
boolean

Keep (or drop) the always-1px transparent border the panel reserves so a border appearing or changing never shifts layout. Computed automatically — it's reserved when a resting `border` is visible, or when `interactive` / `borderInteract` / `active` can change the border at runtime. Set `false` only on a panel that must not occupy that 1px (e.g. a borderless housing frame that would otherwise add 2px around a set).

selected
boolean
default false

Selected state — renders a dashed border. Only visible when the card also has a `border` (e.g. `border="default"`); the dashed style rides the existing ring rather than adding one.

shadow
"sm""md""lg""xl""none""xs"

Elevation shadow applied to the surface.

size
"sm""md""lg""xs""2xs"
default "md"

Controls the padding of the header, content, and footer sections (and the corner-overlay inset). Scales from `2xs` (densest) through `xs` / `sm` / `md` (default) to `lg` (most generous).

translucent
boolean

Apply a frosted-glass effect with backdrop blur over a surface background.

Plus all standard <div> HTML attributes.

Type

  • Components
  • Blocks