Post title
Summary
A surface container for grouped content. Compose freely inside — heading, text, button, an optional media block.
Last updated
A short supporting description of what this card links to or explains.
<Card>
<Heading as="h3">Card title</Heading>
<Text color="neutral-faded">A short supporting description of what this card links to or explains.</Text>
</Card>npx @ava-ui/apogee add card
No Apogee dependencies. Commonly composed with Heading, Text, Button, and Image.
---
import Card from '@/components/apogee/Card/Card.astro'
import Heading from '@/components/apogee/Heading/Heading.astro'
---
<Card>
<Heading as="h3">Card title</Heading>
<p>Body copy.</p>
</Card>
Card is a single component with named slots for its regions, not a Card.Header/Card.Body family:
<a> (when href is set) or <div> surface: border, radius,
background. Any content you pass goes directly inside its padded body, in
the default slot.slot="media" (optional) — renders at the top, above the body, inset 4px with matching corners,
clipped to a 16:9 (aspect-video) box; with mediaPosition="start" it
becomes a fixed-width column beside the body from sm. Meant for an
<img> — the slot content is sized to fill it (size-full object-cover).slot="header" (optional) — a bordered row above the body, stacked on
phones and a row from md, for a title, a badge, actions.slot="footer" (optional) — a bordered row below the body for links or
buttons; put class="ms-auto" on the group that should sit at the end.@composable — exposed to Content Editors in AVA. Any other native <a> /
<div> attribute (e.g. target, rel, aria-label) passes through.
| Prop | Type | Default | Description |
|---|---|---|---|
href |
string |
— | Renders the card as an <a> and makes the whole surface a link (hover/focus states apply). Omit for a static <div>. |
as |
'div' | 'article' | 'section' | 'li' | 'button' |
'div' |
Tag to render; button makes the whole card a button (ignored when href is set). |
raised |
boolean |
false |
Adds the raised shadow. |
selected |
boolean |
false |
Primary border, for a chosen card in a set. |
padding |
'none' | 'small' | 'medium' | 'large' |
'medium' |
Body padding; header and footer rows use the same horizontal step with a shorter vertical one. With none (a flush table or image) the regions keep the medium step. |
mediaPosition |
'top' | 'start' |
'top' |
Where the media slot sits: above the body, or a 15rem column beside it from sm. |
class |
string |
— | Extra classes, merged last. |
The only structural variant is presence of the media slot:
An image slotted above the body, inset 4px from the card’s edges.
Just the padded body.
<Card>
<img slot="media" src="https://picsum.photos/seed/apogee/640/360" alt="" />
<Heading as="h3">With media</Heading>
<Text color="neutral-faded" size="small">An image slotted above the body, inset 4px from the card's edges.</Text>
</Card>
<Card>
<Heading as="h3">Without media</Heading>
<Text color="neutral-faded" size="small">Just the padded body.</Text>
</Card>Header and footer regions:
Manage who has access to this project.
Four people can edit; two can only view.
<Card>
<div slot="header">
<Heading as="h3">Team members</Heading>
<Text color="neutral-faded" size="small">Manage who has access.</Text>
</div>
<Button slot="header" size="small" variant="outline" color="neutral">Invite</Button>
<Text size="small">Body…</Text>
<Button slot="footer" variant="link" class="ms-auto">View all</Button>
</Card>Media beside the body (mediaPosition="start"), stacked on phones:
A fixed-width image column beside the body from the sm breakpoint; above it on phones.
<Card mediaPosition="start">
<img slot="media" src="…" alt="" />
<Heading as="h3">Side media</Heading>
<Text color="neutral-faded" size="small">A fixed-width image column from sm.</Text>
</Card>Every padding:
none
small
medium
large
<Card padding="none">…</Card>
<Card padding="small">…</Card>
<Card padding="medium">…</Card>
<Card padding="large">…</Card>A raised card and a selected card:
Raised
Selected
<Card raised>…</Card>
<Card selected>…</Card>An article card and a button card:
Summary
<Card as="article"><h3>Post title</h3><p>Summary</p></Card>
<Card as="button" selected>Pick this plan</Card>Interactive states only apply when href is set (the whole card is a link):
--background-neutral-faded-highlighted.focus-visible ring (--outline-color-focus).href) — no hover/focus treatment; it’s a plain container.<Card href="#">
<Heading as="h3">Clickable card</Heading>
<Text color="neutral-faded" size="small">Hover or focus to see the interactive state.</Text>
</Card>As a link card with media and a call to action:
<Card href="#">
<img slot="media" src="https://picsum.photos/seed/apogee2/640/360" alt="" />
<Heading as="h3">Ship faster with Apogee</Heading>
<Text color="neutral-faded" size="small">Copy-in, accessible Astro components.</Text>
</Card>Composed with a Button (card itself is static — the button is the click target):
Unlock more seats and priority support.
<Card>
<Heading as="h3">Upgrade your plan</Heading>
<Text color="neutral-faded" size="small">Unlock more seats and priority support.</Text>
<Button class="mt-4">Upgrade</Button>
</Card>A card header with a title, a count, actions and a kebab menu (the kebab is absolute on phones so the title keeps the first line):
Everything billed this quarter.
Table or list goes here.
<Card>
<div slot="header" class="relative flex min-w-0 flex-1 flex-col gap-1 md:pe-0 pe-10">
<div class="flex items-center gap-2">
<Heading as="h3">Invoices</Heading>
<Badge size="small" variant="outline" color="neutral">24</Badge>
</div>
<Text color="neutral-faded" size="small">Everything billed this quarter.</Text>
</div>
<div slot="header" class="flex items-center gap-2">
<Button size="small" variant="outline" color="neutral">Export</Button>
<Button size="small">New invoice</Button>
<Dropdown id="card-kebab" label="More actions" placement="bottom-end">
<Button slot="anchor" popovertarget="card-kebab" size="small" variant="ghost" color="neutral" iconOnly aria-label="More"><EllipsisVertical /></Button>
<DropdownItem>Archive all</DropdownItem>
</Dropdown>
</div>
…
</Card>href, the whole surface is a real <a> — keyboard-focusable,
Enter navigates, no div click-handler faked.href, it’s a plain <div> — don’t add a click handler to fake
navigation; use href instead so it stays keyboard-operable.href card — nested
interactive controls are invalid HTML and confuse both mouse and keyboard
users. Keep linked cards content-only, or keep the card static and put the
single action inside.media images need real alt text (or alt="" if purely decorative).| Token | Used for |
|---|---|
--border-neutral / --border-primary |
Card outline / selected outline |
--background-elevation-raised / --foreground-neutral |
Card background / default text color |
--background-neutral-faded-highlighted |
Hover background (linked cards only) |
--outline-color-focus |
Focus ring (linked cards only) |
--border-neutral |
Header and footer rules |
--radius-lg |
Corner radius |
--shadow-surface / --shadow-raised |
Resting edge (a skin hook) / raised lift |
href when the whole card should be one click target — don’t bury the only action in a nested link.href card.Heading, 2–6 words), not a full sentence.