Card
A composable surface for grouping related content — pricing tiers, profile blocks, stats, callouts, project tiles. The header / body / footer slots compose freely, and the footer anchors to the bottom whenever the card has vertical slack (which makes pricing rows and stat grids line up without any extra wrapper).
Free
Get started in a minute.
1 site, watermarked, hosted on boogie.ro.
Boogie Pro New
For teams who ship motion-rich sites.
Unlimited sites, custom domain, AI rewrites, premium stock library, automatic accessibility checks, and round-the-clock motion support — no watermark, ever.
Anatomy
A Card is just a flex column. The named subcomponents — CardHeader, CardTitle, CardDescription, CardFooter — give you typography and spacing defaults that match the rest of the kit, but everything is optional. Drop in whatever children you want; the only structural rule is that CardFooter auto-pins to the bottom of the card.
Card title
Card description goes here.
Body content sits between the header and the footer.
import {
Card,
CardHeader,
CardTitle,
CardDescription,
CardFooter,
Button,
} from '@bwo-ui/react';
<Card>
<CardHeader>
<CardTitle>Card title</CardTitle>
<CardDescription>Card description goes here.</CardDescription>
</CardHeader>
<p>Body content sits between the header and the footer.</p>
<CardFooter>
<Button size="sm" variant="ghost">Action</Button>
<Button size="sm" variant="primary">Primary</Button>
</CardFooter>
</Card>Interactive
Add the interactive prop to opt into the hover lift treatment — pointer cursor, a deeper shadow, and a 2 px translateY on hover. Use it whenever the entire card is clickable. For accessibility, wire up the click target yourself (typically by wrapping the card in a Link or attaching onClick + role="button" + tabIndex; see the Accessibility section below).
Static
No hover effect — display only.
Renders as a passive surface.
Interactive
Hover for the lift effect.
Cursor turns into a pointer, shadow deepens, and the card lifts 2 px.
<Card>{/* static */}</Card>
<Card interactive>{/* hover-lifted */}</Card>Padding modes
pad controls the inner padding. The default (24 px) is sized for prose-heavy layouts; compact (12 px) packs cards into dense grids; none lets edge-to-edge content like CardMedia bleed to the card's rounded corners.
Default
24 px inner padding.
Compact
12 px inner padding. Use in dense grids.
None
Edge-to-edge media.
<Card>{/* default — 24 px */}</Card>
<Card pad="compact">{/* 12 px */}</Card>
<Card pad="none">
<CardMedia aspect="4 / 3">…</CardMedia>
<div style={{ padding: 14 }}>edge-to-edge media + manually padded text</div>
</Card>Corner radius
The radius prop accepts the shared radius scale (none / sm / md / lg). Omit it to inherit --bwo-radius-current (defaults to 6 px). Picking a sharper radius is a common cue for editorial / project tiles; rounder cards feel softer and more consumer-app.
radius="none"
Tunes the corner curve.
radius="sm"
Tunes the corner curve.
radius="md"
Tunes the corner curve.
radius="lg"
Tunes the corner curve.
<Card radius="none">…</Card>
<Card radius="sm">…</Card>
<Card radius="md">…</Card>
<Card radius="lg">…</Card>Footer alignment
CardFooter uses margin-top: auto inside the card's flex column. The practical effect: whenever the card has any vertical slack (typically because its parent stretches sibling cards to equal height — the default for CSS grids), the footer hugs the bottom. The cards below all have different body lengths, but their footers line up.
Starter
One line.
Growth
A few lines of supporting copy that takes a bit more vertical space than the first card.
Scale
A noticeably longer body section that pushes the natural flow further down, with enough copy to wrap across multiple lines and demonstrate that the footer still anchors to the bottom regardless of how tall the card has to grow.
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 12 }}>
<Card>
<CardHeader><CardTitle>Starter</CardTitle></CardHeader>
<p>Short body.</p>
<CardFooter><Button size="sm">Pick</Button></CardFooter>
</Card>
<Card>
<CardHeader><CardTitle>Growth</CardTitle></CardHeader>
<p>Medium body that wraps over two or three lines.</p>
<CardFooter><Button size="sm">Pick</Button></CardFooter>
</Card>
<Card>
<CardHeader><CardTitle>Scale</CardTitle></CardHeader>
<p>A noticeably longer paragraph of body copy that wraps across many lines…</p>
<CardFooter><Button size="sm">Pick</Button></CardFooter>
</Card>
</div>The behaviour kicks in automatically when the card has slack. In a standalone card sized purely by content, the footer simply sits beneath the body — same as before. No prop required.
Tile layout
For project / template tiles where the media bleeds to the card edges, switch to pad="none" and use the dedicated tile subcomponents: CardMedia, CardTab, CardEyebrow, CardName, CardCaption.
BOOGIE 0001
Agency
Studio · Light
BOOGIE 0002
Basalt
Construction
BOOGIE 0003
Silica
Tech · Light
import {
Card,
CardMedia,
CardTab,
CardEyebrow,
CardName,
CardCaption,
MediaZoom,
} from '@bwo-ui/react';
<Card pad="none" radius="sm" interactive>
<CardMedia aspect="3 / 4">
<MediaZoom>
<img src="/tile.jpg" alt="" />
</MediaZoom>
</CardMedia>
<CardTab>
<CardEyebrow>BOOGIE 0001</CardEyebrow>
<CardName>Agency</CardName>
<CardCaption>Studio · Light</CardCaption>
</CardTab>
</Card>Recipes
Stat card
A common analytics pattern: uppercase eyebrow + display-font value + small delta line. Compose it directly from a Card — no special prop required.
Monthly visitors
128.4k
+12.3% vs last month
Active sites
4,910
+218 vs last month
Bounce rate
38%
−2.1% vs last month
<Card pad="compact">
<p style={{ fontSize: 11, textTransform: 'uppercase', letterSpacing: '0.08em', color: 'var(--bwo-text-body)' }}>
Monthly visitors
</p>
<p style={{ fontSize: 28, fontWeight: 700, letterSpacing: '-0.02em', margin: '6px 0 4px' }}>
128.4k
</p>
<p style={{ fontSize: 12, fontWeight: 600, color: 'var(--bwo-green)' }}>
+12.3% vs last month
</p>
</Card>Profile card
Avatar + name + role on top, action buttons in the footer. The footer's automatic bottom-alignment is what makes these cards line up cleanly in a directory grid.
Ana Radu
Lead motion designer · Boogie
<Card>
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
<Avatar />
<div>
<p style={{ fontSize: 15, fontWeight: 600 }}>Ana Radu</p>
<p style={{ fontSize: 13, color: 'var(--bwo-text-body)' }}>Lead motion designer</p>
</div>
</div>
<CardFooter>
<Button size="sm" variant="outline">Message</Button>
<Button size="sm" variant="primary">Follow</Button>
</CardFooter>
</Card>Quote / callout
For testimonial blocks or pull quotes, drop the structured subcomponents and just write prose. The Card's padding and shadow do the visual work.
“bwo-ui shipped 60 components in a month. Every primitive is ours — no Radix, no cmdk, no day-picker. It just feels like one library.”
Mihai Stoica
Frontend lead · boogie.ro
<Card radius="md">
<p style={{ fontSize: 18, lineHeight: 1.45, fontWeight: 500 }}>
“bwo-ui shipped 60 components in a month. Every primitive is ours…”
</p>
<div style={{ display: 'flex', gap: 10, marginTop: 16, paddingTop: 16, borderTop: '1px solid var(--bwo-border)' }}>
<Avatar />
<div>
<p style={{ fontSize: 13, fontWeight: 600 }}>Mihai Stoica</p>
<p style={{ fontSize: 12, color: 'var(--bwo-text-body)' }}>Frontend lead · boogie.ro</p>
</div>
</div>
</Card>Aligning footer actions
CardFooter is a flexbox row. It defaults to left-aligned content — override with justifyContent when you want actions on the right or split across both sides.
{/* Right-aligned actions */}
<CardFooter style={{ justifyContent: 'flex-end' }}>
<Button size="sm" variant="ghost">Cancel</Button>
<Button size="sm" variant="primary">Save</Button>
</CardFooter>
{/* Split — meta info on the left, primary action on the right */}
<CardFooter style={{ justifyContent: 'space-between' }}>
<span style={{ fontSize: 12, color: 'var(--bwo-text-body)' }}>Updated 2 h ago</span>
<Button size="sm" variant="primary">Open</Button>
</CardFooter>Accessibility
- A card without
interactiveis a passive surface and needs no extra a11y wiring. Use it for grouping, not for actions. - When the whole card is clickable, the cleanest pattern is to wrap the Card in a
<Link>(or<a>) — the link is the focusable, screen-reader-readable interactive element, and the Card just supplies the visual surface. - If you must make the card itself the click target, add
role="button",tabIndex=0, and a keyboard handler (Enter/Space→ click). Otherwise keyboard users cannot activate it. - Avoid nesting interactive elements (e.g. a Button inside an interactive Card) without stopping propagation — clicking the inner button will also trigger the card's handler, which is rarely what users want.
- Decorative
CardMediaimages should carryalt=""; meaningful ones (photos with context relevant to the card body) should carry descriptive alt text.
Card props
| Prop | Type | Default | Description |
|---|---|---|---|
interactive | boolean | false | Adds the hover lift effect — pointer cursor, deeper shadow, 2 px translateY. Does not add a11y wiring; see the Accessibility section. |
radius | 'none' | 'sm' | 'md' | 'lg' | 'pill' | — | Corner radius preset. Omit to inherit `--bwo-radius-current` (defaults to 6 px). |
pad | 'default' | 'compact' | 'none' | 'default' | 'default' = 24 px, 'compact' = 12 px, 'none' = no padding (use with `CardMedia` for edge-to-edge media). |
…rest | HTMLAttributes<HTMLDivElement> | — | Every native div attribute is forwarded — `onClick`, `style`, etc. |
Subcomponents
| Prop | Type | Default | Description |
|---|---|---|---|
CardHeader | <div> | — | Top section above the body. 12 px margin-bottom. |
CardTitle | <h3> | — | 18 px / 600 weight heading. |
CardDescription | <p> | — | 14 px muted line under the title. |
CardFooter | <div> | — | Bottom section. Auto-pins to the bottom of the card (margin-top: auto) and has a 1 px top divider. Flex row with 8 px gap; align actions via `justifyContent` in inline style. |
CardMedia | <div aspect> | — | Top media slot. Wraps an inner `<img>` / `<video>` with object-fit: cover. Aspect defaults to 3/4. Pairs with `pad="none"` for edge-to-edge media. |
CardTab | <div> | — | Strip beneath `CardMedia` — typically eyebrow + name + caption. |
CardEyebrow | <p> | — | Small uppercase code or label above the name. |
CardName | <p> | — | Display-font name line. |
CardCaption | <p> | — | Tiny uppercase category / status tag. |