Card
Moleculebeta@macaw-ds/ui · since 0.1.0A bordered surface that groups related content: a KPI, a form section, a record summary. Not a page, not a modal.
Import
import { Card, CardHeader, CardTitle, CardDescription, CardBody, CardFooter, cardVariants } from '@macaw-ds/ui';Props
| Prop | Type | Default | Description |
|---|---|---|---|
| padding | "none" | "sm" | "md" | md | Inner padding for the sections. none when the body is a table or an image that should touch the edges. |
| elevation | "flat" | "raised" | flat | flat relies on the border; raised adds a subtle shadow for cards that float over the canvas. |
| interactive | boolean | false | Hover border and the focus ring. Use with asChild so the card is an anchor or a button. Accessibility: An interactive card must be an anchor or a button (asChild) so it has a role and keyboard activation. |
| asChild | boolean | false | Render the child element as the card. |
| children | ReactNode | — | Card.Header, Card.Body, Card.Footer in that order. |
Slots
Named parts you pass content into.
- Header
- Title
- Description
- Body
- Footer
Tokens used
Every value this component renders comes from one of these tokens.
| Token | Light | Dark |
|---|---|---|
| card.radius component · dimension | 6px | 6px |
| card.bg.default component · color | oklch(1 0 0) | oklch(0.15 0.012 250) |
| card.line component · color | oklch(0.925 0.005 250) | oklch(0.29 0.012 250) |
| card.shadow.flat component · shadow | 0px 0px 0px 0px oklch(0 0 0 / 0) | 0px 0px 0px 0px oklch(0 0 0 / 0) |
| card.shadow.raised component · shadow | 0px 1px 2px 0px oklch(0.15 0.012 250 / 0.06), 0px 2px 6px -1px oklch(0.15 0.012 250 / 0.06) | 0px 1px 2px 0px oklch(0.15 0.012 250 / 0.06), 0px 2px 6px -1px oklch(0.15 0.012 250 / 0.06) |
| color.line.subtle semantic · color | oklch(0.965 0.003 250) | oklch(0.21 0.012 250) |
| color.line.strong semantic · color | oklch(0.53 0.012 250) | oklch(0.53 0.012 250) |
| color.surface.layer-2 semantic · color | oklch(0.985 0.002 250) | oklch(0.21 0.012 250) |
| color.focus.ring semantic · color | oklch(0.51 0.2 262) | oklch(0.68 0.16 262) |
| layout.card-x semantic · dimension | 20px | 20px |
| layout.card-y semantic · dimension | 16px | 16px |
| layout.card-x-sm semantic · dimension | 16px | 16px |
| layout.card-y-sm semantic · dimension | 12px | 12px |
Eject
Ejecting copies the source into your repo and records it in .macaw/ejected.json, so the fork stays visible. The same file is served as a shadcn-compatible registry item.
npx @macaw-ds/cli add card --ejectnpx shadcn@latest add https://macaw-design-system.vercel.app/r/card.jsonBundle budget: 3,000 bytes gzipped, enforced by size-limit in CI.
Manifest
registry/manifests/card.json, the contract the playground, CLI, plugin and MCP server read.
{
"name": "Card",
"slug": "card",
"package": "@macaw-ds/ui",
"status": "beta",
"since": "0.1.0",
"level": "molecule",
"category": "layout",
"purpose": "A bordered surface that groups related content: a KPI, a form section, a record summary. Not a page, not a modal.",
"description": "A compound component. `Card` is the surface; `Card.Header`, `Card.Title`, `Card.Description`, `Card.Body` and `Card.Footer` lay out its parts with the padding the Card sets. Structure comes from a 1 px border; elevation is optional and subtle. `interactive` with `asChild` turns the whole card into a link or button with the shared focus ring. Padding follows the density mode.",
"exports": [
"Card",
"CardHeader",
"CardTitle",
"CardDescription",
"CardBody",
"CardFooter",
"cardVariants"
],
"props": {
"padding": {
"type": "enum",
"values": [
"none",
"sm",
"md"
],
"default": "md",
"required": false,
"description": "Inner padding for the sections. none when the body is a table or an image that should touch the edges."
},
"elevation": {
"type": "enum",
"values": [
"flat",
"raised"
],
"default": "flat",
"required": false,
"description": "flat relies on the border; raised adds a subtle shadow for cards that float over the canvas."
},
"interactive": {
"type": "boolean",
"default": false,
"required": false,
"description": "Hover border and the focus ring. Use with asChild so the card is an anchor or a button.",
"a11y": "An interactive card must be an anchor or a button (asChild) so it has a role and keyboard activation."
},
"asChild": {
"type": "boolean",
"default": false,
"required": false,
"description": "Render the child element as the card.",
"control": "none"
},
"children": {
"type": "node",
"required": false,
"description": "Card.Header, Card.Body, Card.Footer in that order.",
"control": "none"
}
},
"slots": [
"Header",
"Title",
"Description",
"Body",
"Footer"
],
"tokens": [
"card.radius",
"card.bg.default",
"card.line",
"card.shadow.flat",
"card.shadow.raised",
"color.line.subtle",
"color.line.strong",
"color.surface.layer-2",
"color.focus.ring",
"layout.card-x",
"layout.card-y",
"layout.card-x-sm",
"layout.card-y-sm"
],
"a11y": {
"role": "none for a static card; link or button when interactive with asChild",
"focusable": {
"whenProp": "interactive"
},
"keyboard": [
"Static cards are not focusable",
"Interactive cards (asChild anchor or button): Tab to focus, Enter (and Space for buttons) to activate"
],
"name": "Static cards have none. Interactive cards take the name from their text content; keep the title first so it is read first.",
"notes": [
"Card.Title renders an h3 by default. Set `as` to keep heading levels in order on the page.",
"Do not nest interactive elements inside an interactive card; a card that is a link cannot contain buttons.",
"The footer's sunken background keeps 4.5:1 for primary and secondary text."
]
},
"motion": {
"description": "An interactive card eases its border colour on hover.",
"tokens": [
"duration.fast",
"ease.standard"
],
"whenProp": "interactive"
},
"budget": {
"gzip": 3000
},
"rules": [
"Use the sections (Header, Body, Footer) rather than custom padding; the Card owns its spacing.",
"Card.Title sets the heading level with `as` to fit the page outline.",
"Interactive cards use asChild with an anchor or a button and contain no other interactive elements.",
"One primary action per card footer at most.",
"From a React Server Component, import the named parts (CardHeader, CardBody, CardFooter); the dotted form (Card.Header) only works inside client components."
],
"dos": [
"Use padding=\"none\" and put a table in Card.Body so rows run edge to edge.",
"Use Card.Description for a one-line summary under the title."
],
"donts": [
"Do not nest cards.",
"Do not use elevation to signal importance; use position and heading level."
],
"examples": [
{
"name": "Default",
"props": {}
},
{
"name": "Raised",
"props": {
"elevation": "raised"
}
},
{
"name": "Compact",
"props": {
"padding": "sm"
}
},
{
"name": "Interactive",
"description": "The whole card is a link.",
"props": {
"interactive": true
}
}
],
"registryDependencies": [
"text"
],
"dependencies": [
"radix-ui",
"class-variance-authority"
],
"related": [
"text",
"button",
"badge"
]
}