MD3
Expressive
MATERIAL DESIGN 3 EXPRESSIVE

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.
Loading demo...

Layout Patterns (Media & Text / Horizontal)

Cards can be structured vertically (Stacked) or horizontally, accommodating rich media headers and compact list-item tiles.

Loading demo...

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} or disableElevation) or increase emphasis (elevation={3}).
  • Ripple Control: Enable or disable ripple feedback with ripple={false} or disableRipple.
  • Hover & State Layer: Disable hover elevation changes (disableHoverEffect) or state layer overlays (disableStateLayer).
  • Motion Integration: Seamlessly merge user whileHover, whileTap, and transition props.
Loading demo...

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> when onClick is provided. Features elevation transitions and MD3 ripple.
  • Link: Renders as <a> when href is provided. Automatically appends rel="noreferrer" for target="_blank".
  • Draggable: Opt-in via draggable={true} to support Level 3 elevation and 16% drag state layer overlay.
Loading demo...

Disabled State

Loading demo...

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 href for navigation and onClick for 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 Tab and activatable via Enter/Space.
  • ARIA & Roles: Semantics adapt automatically (role="button", role="link"). Static cards preserve all custom aria-* and data-* attributes.
  • Disabled State: Sets aria-disabled="true", removes href on links, and excludes from tab order (tabIndex={-1}).
  • Focus Rings: Outline-free MD3 focus state overlay + elevation morphing.

API Reference

Card

PropTypeDefaultDescription
variant"elevated" | "filled" | "outlined""elevated"Visual style of the card.
elevation0 | 1 | 2 | 3 | 4 | 5 | { rest?: number; hover?: number; pressed?: number; dragged?: number }Numeric elevation override (0 = none/flat) or per-state elevation map.
disableElevationbooleanfalseDisables all box-shadow elevation effects.
ripplebooleantrueControls whether ripple is active on interactive cards.
disableRipplebooleanfalseShorthand to disable ripple feedback.
hoverEffectbooleantrueControls whether hover elevates the card.
disableHoverEffectbooleanfalseShorthand to disable hover elevation changes.
stateLayerbooleantrueControls whether MD3 state layer overlays are rendered.
disableStateLayerbooleanfalseShorthand to disable state layer overlays.
onClick(event: React.MouseEvent) => voidMakes the card a <button> with ripple and elevation.
hrefstringMakes the card a link (<a>).
targetstringTarget attribute for links (e.g., "_blank").
relstringLink relationship attribute (defaults to "noreferrer" when target="_blank").
interactivebooleanfalseForces button semantics and ripple even without onClick.
draggablebooleanfalseEnables MD3 Level 3 elevation and 16% state layer on drag.
disabledbooleanfalseDisables interactions and reduces container opacity to 38%.
asChildbooleanfalseMerges styles and behaviors into the immediate child element.
whileHoverTargetAndTransitionCustom Framer Motion hover state (merged with elevation).
whileTapTargetAndTransitionCustom Framer Motion tap state (merged with elevation).
classNamestringAdditional CSS classes for custom styling.

CardHeader

PropTypeDefaultDescription
avatarReact.ReactNodeLeading avatar, icon, or thumbnail slot.
titleReact.ReactNodePrimary headline text.
subheaderReact.ReactNodeSecondary subtitle text below the title.
actionReact.ReactNodeTrailing action slot (e.g. icon button or menu trigger).
classNamestringAdditional CSS classes.

CardMedia

PropTypeDefaultDescription
srcstringImage or media source URL.
altstring""Alternative text for screen readers.
aspectRatio"video" | "square" | "wide" | "auto" | string"video"Aspect ratio preset for the media container.
componentReact.ElementTypesrc ? "img" : "div"Element or component override (e.g. video, Next.js Image).
classNamestringAdditional CSS classes.

CardContent

PropTypeDefaultDescription
classNamestringAdditional CSS classes for custom padding or text layout.

CardFooter

PropTypeDefaultDescription
align"start" | "center" | "end" | "between""end"Flex alignment for action buttons.
classNamestringAdditional CSS classes.