A surface container for grouped content. Compose freely inside — heading, text, button, an optional media block.

Last updated

Card title

A short supporting description of what this card links to or explains.

Show code
<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>

Add

npx @ava-ui/apogee add card

No Apogee dependencies. Commonly composed with Heading, Text, Button, and Image.

Usage

---
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>

Anatomy

Card is a single component with named slots for its regions, not a Card.Header/Card.Body family:

  • Root — the <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.

API

@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.

Variants

The only structural variant is presence of the media slot:

With media

An image slotted above the body, inset 4px from the card’s edges.

Without media

Just the padded body.

Show code
<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:

Team members

Manage who has access to this project.

Four people can edit; two can only view.

Show code
<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:

Side media

A fixed-width image column beside the body from the sm breakpoint; above it on phones.

Show code
<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

Show code
<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

Show code
<Card raised>…</Card>
<Card selected>…</Card>

An article card and a button card:

Post title

Summary

Show code
<Card as="article"><h3>Post title</h3><p>Summary</p></Card>
<Card as="button" selected>Pick this plan</Card>

States

Interactive states only apply when href is set (the whole card is a link):

  • Hover — background shifts to --background-neutral-faded-highlighted.
  • Focus — a visible focus-visible ring (--outline-color-focus).
  • Static (no href) — no hover/focus treatment; it’s a plain container.
Show code
<Card href="#">
<Heading as="h3">Clickable card</Heading>
<Text color="neutral-faded" size="small">Hover or focus to see the interactive state.</Text>
</Card>

Examples

As a link card with media and a call to action:

Show code
<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):

Upgrade your plan

Unlock more seats and priority support.

Show code
<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):

Invoices

24

Everything billed this quarter.

Table or list goes here.

Show code
<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>

Accessibility

  • With href, the whole surface is a real <a> — keyboard-focusable, Enter navigates, no div click-handler faked.
  • Without href, it’s a plain <div> — don’t add a click handler to fake navigation; use href instead so it stays keyboard-operable.
  • If a linked card also contains its own interactive children (a Button), avoid nesting a second link/button inside an 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).

Tokens

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

Do / Don’t

  • Do use href when the whole card should be one click target — don’t bury the only action in a nested link.
  • Do keep card content short — a heading, a line or two, maybe one action.
  • Don’t nest another link or button inside an href card.
  • Don’t mix unrelated content types in a grid of cards — keep each card’s shape consistent within a set.

Content

  • Title is a short noun phrase (Heading, 2–6 words), not a full sentence.
  • Supporting text is one to two lines — truncate or link to “Read more” rather than letting a card grow tall in a grid.