Cards
Cards contain content and actions about a single subject. Expressive cards use motion, elevation, and depth to communicate information hierarchy and interactivity.
Cards are flexible containers that group related information and actions. They adapt their rendering element based on interactivity — static for display, <button> for actions, or <a> for navigation. Expressive cards animate elevation on hover, press, or drag, and feature MD3 state-layer and Ripple effects.
Introduction
The MD3 Expressive Card is a fundamental unit of UI that helps organize content into digestible blocks. It supports three primary variants: Elevated, Filled, and Outlined. Beyond just layout, cards provide a modular anatomy consisting of CardHeader, CardMedia, CardContent, and CardFooter to easily compose rich stacked and horizontal cards matching the Material 3 design system.
Anatomy
┌────────────────────────────────────────────────────────┐
│ Card (Container) │
│ ┌──────────────────────────────────────────────────┐ │
│ │ CardHeader (Avatar + Title/Subhead + Action) │ │
│ └──────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ CardMedia (Image / Video / Custom Aspect Ratio) │ │
│ └──────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ CardContent (Body text / Child components) │ │
│ └──────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ CardFooter (Action buttons / Secondary + Primary)│ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────┘
Variants
Color Styles
- Elevated: High emphasis, uses a dynamic drop shadow (
SurfaceContainerLow). Best for standout content on plain backgrounds. - Filled: Medium emphasis, uses a distinct surface color fill (
SurfaceContainerHighest). Good for grouping related items. - Outlined: Low emphasis, uses a subtle outline boundary (
OutlineVariant). Best for dense layouts and data-heavy grids.
Layout Patterns (Media & Text / Horizontal)
Cards can be structured vertically (Stacked) or horizontally, accommodating rich media headers and compact list-item tiles.
Customization & Flexibility
Cards offer rich customization options for elevation levels, ripple behavior, hover animations, and custom Framer Motion props:
- Custom Elevation: Force flat cards (
elevation={0}ordisableElevation) or increase emphasis (elevation={3}). - Ripple Control: Enable or disable ripple feedback with
ripple={false}ordisableRipple. - Hover & State Layer: Disable hover elevation changes (
disableHoverEffect) or state layer overlays (disableStateLayer). - Motion Integration: Seamlessly merge user
whileHover,whileTap, andtransitionprops.
Interactive Types
The Card component automatically selects the correct element and interaction layer:
- Static: Renders as
<div>. Use when the card is purely for display or contains nested interactive elements. - Button: Renders as
<button>whenonClickis provided. Features elevation transitions and MD3 ripple. - Link: Renders as
<a>whenhrefis provided. Automatically appendsrel="noreferrer"fortarget="_blank". - Draggable: Opt-in via
draggable={true}to support Level 3 elevation and 16% drag state layer overlay.
Disabled State
Usage
Composing with Sub-components
import {
Button,
Card,
CardContent,
CardFooter,
CardHeader,
CardMedia,
Icon,
} from "@bug-on/m3-expressive";
export function ProductCard() {
return (
<Card variant="elevated" className="max-w-sm">
<CardHeader
avatar={<div className="w-10 h-10 rounded-full bg-m3-primary-container text-m3-primary flex items-center justify-center font-medium">P</div>}
title="Product Name"
subheader="Category • $49.99"
action={
<button type="button" aria-label="Favorite" className="p-1 rounded-full text-m3-on-surface-variant hover:text-m3-on-surface">
<Icon name="favorite" size={20} />
</button>
}
/>
<CardMedia src="/product.jpg" alt="Product preview" aspectRatio="video" />
<CardContent>
<p className="text-sm text-m3-on-surface-variant">
Detailed product description and key features highlighting the item.
</p>
</CardContent>
<CardFooter align="end">
<Button colorStyle="text">Details</Button>
<Button colorStyle="filled">Add to Cart</Button>
</CardFooter>
</Card>
);
}
Flat Card without Elevation
<Card variant="elevated" elevation={0} className="p-6">
<h3 className="text-base font-medium text-m3-on-surface">Flat Card</h3>
<p className="text-sm text-m3-on-surface-variant">Zero elevation drop shadow.</p>
</Card>
Clickable Card without Ripple
<Card
onClick={() => console.log("Clicked")}
variant="filled"
disableRipple
className="p-6"
>
<h3 className="text-base font-medium text-m3-on-surface mb-1">No Ripple</h3>
<p className="text-sm text-m3-on-surface-variant">Interactivity without ripple animation.</p>
</Card>
Custom Motion Scale on Hover
<Card
onClick={() => console.log("Clicked")}
variant="elevated"
whileHover={{ scale: 1.02, y: -2 }}
whileTap={{ scale: 0.98 }}
className="p-6"
>
<h3 className="text-base font-medium text-m3-on-surface mb-1">Interactive Scale</h3>
<p className="text-sm text-m3-on-surface-variant">Smooth physical spring scale on hover.</p>
</Card>
Best Practices
Do
- Use sub-components (
CardHeader,CardMedia,CardContent,CardFooter) for standard MD3 layout consistency. - Use Outlined cards for dense lists or grids to avoid shadow clutter.
- Preserve semantic roles — use
hreffor navigation andonClickfor actions.
Don't
- Don't nest interactive buttons inside a card that has its own root
onClick(causes nested button issues). Use a static card container when nesting buttons. - Don't overuse Elevated cards on cluttered screens.
- Don't place critical navigation inside non-interactive static cards.
Accessibility
- Keyboard Navigation: Interactive cards (button/link) are focusable via
Taband activatable viaEnter/Space. - ARIA & Roles: Semantics adapt automatically (
role="button",role="link"). Static cards preserve all customaria-*anddata-*attributes. - Disabled State: Sets
aria-disabled="true", removeshrefon links, and excludes from tab order (tabIndex={-1}). - Focus Rings: Outline-free MD3 focus state overlay + elevation morphing.
API Reference
Card
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "elevated" | "filled" | "outlined" | "elevated" | Visual style of the card. |
elevation | 0 | 1 | 2 | 3 | 4 | 5 | { rest?: number; hover?: number; pressed?: number; dragged?: number } | — | Numeric elevation override (0 = none/flat) or per-state elevation map. |
disableElevation | boolean | false | Disables all box-shadow elevation effects. |
ripple | boolean | true | Controls whether ripple is active on interactive cards. |
disableRipple | boolean | false | Shorthand to disable ripple feedback. |
hoverEffect | boolean | true | Controls whether hover elevates the card. |
disableHoverEffect | boolean | false | Shorthand to disable hover elevation changes. |
stateLayer | boolean | true | Controls whether MD3 state layer overlays are rendered. |
disableStateLayer | boolean | false | Shorthand to disable state layer overlays. |
onClick | (event: React.MouseEvent) => void | — | Makes the card a <button> with ripple and elevation. |
href | string | — | Makes the card a link (<a>). |
target | string | — | Target attribute for links (e.g., "_blank"). |
rel | string | — | Link relationship attribute (defaults to "noreferrer" when target="_blank"). |
interactive | boolean | false | Forces button semantics and ripple even without onClick. |
draggable | boolean | false | Enables MD3 Level 3 elevation and 16% state layer on drag. |
disabled | boolean | false | Disables interactions and reduces container opacity to 38%. |
asChild | boolean | false | Merges styles and behaviors into the immediate child element. |
whileHover | TargetAndTransition | — | Custom Framer Motion hover state (merged with elevation). |
whileTap | TargetAndTransition | — | Custom Framer Motion tap state (merged with elevation). |
className | string | — | Additional CSS classes for custom styling. |
CardHeader
| Prop | Type | Default | Description |
|---|---|---|---|
avatar | React.ReactNode | — | Leading avatar, icon, or thumbnail slot. |
title | React.ReactNode | — | Primary headline text. |
subheader | React.ReactNode | — | Secondary subtitle text below the title. |
action | React.ReactNode | — | Trailing action slot (e.g. icon button or menu trigger). |
className | string | — | Additional CSS classes. |
CardMedia
| Prop | Type | Default | Description |
|---|---|---|---|
src | string | — | Image or media source URL. |
alt | string | "" | Alternative text for screen readers. |
aspectRatio | "video" | "square" | "wide" | "auto" | string | "video" | Aspect ratio preset for the media container. |
component | React.ElementType | src ? "img" : "div" | Element or component override (e.g. video, Next.js Image). |
className | string | — | Additional CSS classes. |
CardContent
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | — | Additional CSS classes for custom padding or text layout. |
CardFooter
| Prop | Type | Default | Description |
|---|---|---|---|
align | "start" | "center" | "end" | "between" | "end" | Flex alignment for action buttons. |
className | string | — | Additional CSS classes. |