Use an <article> for an elevated card. Inside it, any
heading is the headline, <p class="subhead"> is the
optional subhead, and a <p> is supporting copy. A
direct <img>, <picture>, or
<figure> is media, and a direct .actions
is the action row. Include only the slots the content needs.
There is no card-content, card-title, or
card-action class.
Tokens follow the
M3 card spec.
The elevated container is surface-container-low with
12dp corners. The default sits at elevation 1, and an interactive card
rises to 2 on hover. The
headline is title-medium in on-surface, the
subhead is title-small in on-surface, and
supporting text is body-medium in
on-surface-variant. Inset is 16dp.
Weekend in the mountains
Three-day itinerary
Explore trails, overlooks, and places to rest along the way.
<article>
<header>
<h3>Weekend in the mountains</h3>
<p class="subhead">Three-day itinerary</p>
</header>
<p>Explore trails and overlooks.</p>
<img src="images/mountains.jpg" alt="Mountain valley beneath a cloudy sky">
<div class="actions">
<button type="button" class="text">Share</button>
<button type="button" class="tonal">View trip</button>
</div>
</article>
Variants
Cards are elevated by default. Add filled for
surface-container-highest at rest, with no shadow. Add
outlined (or border) for a 1dp
outline-variant stroke over surface.
Elevated
Surface-container-low, elevation 1.
Filled
Surface-container-highest, no elevation.
Outlined
Surface plus a 1dp outline.
<article>…</article>
<article class="filled">…</article>
<article class="outlined">…</article>
Primary action and states
To make the card an entry point, wrap its primary content in a direct
<a class="primary-action">. That gives the card M3
hover, focus, pressed, and focus-indicator states. A static card has
no hover treatment. A directly actionable card has no other links or
buttons. When the content needs several actions, leave the card
static and put the controls in .actions.
Toggle dragged or picked-up during
reordering. That state keeps its dragged elevation and 16% state
layer even if the primary action remains hovered or pressed. For a
disabled destination, remove href and set both
aria-disabled="true" and tabindex="-1" on
the primary action.
Upcoming reservation
Open the reservation details.
<article>
<a class="primary-action" href="/reservation/42">
<h3>Upcoming reservation</h3>
<p>Open the reservation details.</p>
</a>
</article>
Collections
Add card-collection to a container for a responsive grid
with no more than 8dp between cards. Cards keep their variant-specific resting
elevation: level 1 for elevated, level 0 for filled and outlined.
Add picked-up or dragged only while a card is
being moved. Add list, staggered,
mosaic, or carousel uncontained for the other collection
layouts. Sorting and filtering controls stay outside the collection.
Responsive grid
The default uses auto-fit columns. When the content needs a different column strategy, change the minimum card width or replace the complete grid track token.
Pho
$12
Burrito Bowl
$9
Quinoa Salad
$10
List
list keeps one card per row and 8dp spacing without
changing each card's variant-specific resting elevation.
Pho
Rice noodles, herbs, and aromatic broth · $12
Quinoa Salad
Roasted vegetables, herbs, and lemon dressing · $10
Staggered grid
staggered packs intrinsic card heights
into masonry-like columns. Source order flows down each column.
Pho
$12
Combo #2
Two entrées, three sides, and two drinks · $28
Pumpkin Soup
$8
Combo #1
Entrée, side, and drink · $14
Mosaic grid
mosaic uses dense grid placement. Set
column and row spans on individual cards from application data.
Pho
$12
Combo: Entrée, side, and drink
$14
Combo #2: 2 entrées, 3 sides, and 2 drinks
$28
Quinoa Salad
$10
Carousel
Add carousel uncontained to the collection and
carousel-item to every card. AutoInit()
supplies the M3 scroll track.
Pho
Rice noodles, herbs, and aromatic broth · $12
Quinoa Salad
Roasted vegetables, herbs, and lemon dressing · $10
Combo #1
Entrée, side, and drink · $14
<!-- Responsive grid (the default). Override the track token as needed. -->
<section class="card-collection" aria-label="Dinner menu">
<article class="outlined">…</article>
<article class="outlined">…</article>
</section>
<!-- List -->
<section class="card-collection list" aria-label="Dinner menu">
<article class="outlined"><h3>Pho</h3><p>$12</p></article>
<article class="outlined"><h3>Quinoa Salad</h3><p>$10</p></article>
</section>
<!-- Intrinsic-height staggered grid -->
<section class="card-collection staggered" aria-label="Dinner menu">
<article class="outlined"><h3>Pho</h3><p>$12</p></article>
<article class="outlined"><h3>Combo #2</h3><p>Two entrées, three sides, and two drinks · $28</p></article>
</section>
<!-- Dense mosaic. Set spans per card from application data. -->
<section class="card-collection mosaic" aria-label="Dinner menu">
<article style="--md-comp-card-collection-row-span: 3">…</article>
<article style="--md-comp-card-collection-column-span: 2; --md-comp-card-collection-row-span: 4">…</article>
</section>
<!-- M3 uncontained carousel. AutoInit starts the Carousel component. -->
<section class="card-collection carousel uncontained" aria-label="Dinner menu">
<article class="outlined carousel-item"><h3>Pho</h3><p>$12</p></article>
<article class="outlined carousel-item"><h3>Quinoa Salad</h3><p>$10</p></article>
</section>
Media
A direct <img> is full-bleed across the top, and its
media surface has rounded corners matching the card. To caption the
image, wrap it in a <figure>. The
<figcaption> is an opaque, rounded bounding shape
with the complete title-medium type role, including letter-spacing.
Its tracking follows the title-medium token and can affect text width
or wrapping. The shape uses the paired surface and
on-surface roles, so image colors cannot reduce the
contrast of its text or icons. Normal text must keep at least 4.5:1
contrast. Large text and meaningful icons need at least 3:1. Recheck
those ratios if you override either color token.
I am a very simple card. I am good at containing small bits of information.
<article>
<figure>
<img src="images/sample-1.jpg" alt="">
<figcaption>
<span class="material-symbols" aria-hidden="true">landscape</span>
<span>Card title</span>
</figcaption>
</figure>
<p>I am a very simple card.</p>
<div class="actions">
<button type="button" class="text">Action</button>
</div>
</article>
For an icon that is not a Material Symbol, such as an inline
<svg> or an image, add card-icon. It
sizes the icon to --md-comp-card-icon-size (24px) and
colors it with --md-comp-card-icon-color (the
primary role). Inside a <figcaption>
it takes the caption's text color instead.
Horizontal
Add horizontal to move the media to the start and stack
the headline, supporting text, and action beside it. The content
order stays the same in both orientations. When the whole horizontal
card is one destination, a direct .primary-action link
can wrap those same slots.
Without a size helper, the card's height follows content with a 240px
minimum. small, medium, and
large set fixed expanded heights. Below 600px, both forms
return to the vertical layout and fixed heights reset to content. Inside a
.query-container narrower than 600px they
do the same on any window, so a horizontal card in a sidebar stacks too.
Performances at The Hideout
Watch exclusive live performances at The Hideout every Saturday starting at 7pm.
Performances at The Hideout
Watch exclusive live performances at The Hideout every Saturday starting at 7pm.
<!-- Vertical -->
<article class="outlined">
<img src="images/the-hideout.jpg" alt="Musician playing guitar during a live performance">
<h3>Performances at The Hideout</h3>
<p>Watch exclusive live performances at The Hideout every Saturday starting at 7pm.</p>
<div class="actions">
<button type="button" class="tonal">Get tickets</button>
</div>
</article>
<!-- Horizontal: only the orientation class changes. -->
<article class="outlined horizontal">
<img src="images/the-hideout.jpg" alt="Musician playing guitar during a live performance">
<h3>Performances at The Hideout</h3>
<p>Watch exclusive live performances at The Hideout every Saturday starting at 7pm.</p>
<div class="actions">
<button type="button" class="tonal">Get tickets</button>
</div>
</article>
<!-- Directly actionable horizontal card. -->
<article class="outlined horizontal">
<a class="primary-action" href="/performances/the-hideout">
<img src="images/the-hideout.jpg" alt="Musician playing guitar during a live performance">
<h3>Performances at The Hideout</h3>
<p>Watch exclusive live performances every Saturday.</p>
</a>
</article>
Reveal
Put the details in an <aside>. It expands in normal
flow below the persistent media, headline, and subhead. Place a
.card-reveal-trigger button over the media, and use that
same native button to open and close the panel. The reveal grows the
card instead of covering or internally scrolling it. This replaces
legacy .activator markup and heading-based close controls.
Cards.Init(), and AutoInit() when the card
contains both that button and one identified direct <aside>,
manages aria-expanded. It applies the closed-panel styles
only when the disclosure markup passes these checks. The trigger must be
enabled when Cards initializes. A disclosure that fails, such as one with only
disabled triggers, an unidentified panel, or several panels, leaves every
panel visible, available, and untouched.
The native button handles Enter and Space. Escape closes an open
panel and returns focus to its trigger. A trigger belongs to its
closest <article>. Nested cards, including cards
inside an outer reveal panel, keep their own disclosure ownership and
do not initialize or toggle the outer card.
Cards.init(el, options) takes two callbacks, both
null by default and both called with the card:
onOpen after the panel opens and onClose
after it closes. open() and close() on the
instance do the same as the trigger.
Ana Russo
Sibling
<article class="filled">
<figure>
<img src="images/ana-russo.jpg" alt="Portrait of Ana Russo">
<button type="button"
class="card-reveal-trigger"
aria-label="Toggle contact details"
aria-controls="ana-contact"></button>
</figure>
<header class="card-reveal-summary">
<h3>Ana Russo</h3>
<p class="subhead">Sibling</p>
</header>
<aside id="ana-contact">
<address class="reveal-actions">
<a class="reveal-action" href="tel:+16505551234">
<span class="material-symbols" aria-hidden="true">call</span>
<span>(650) 555-1234</span>
</a>
<a class="reveal-action" href="mailto:hey@anarusso.com">
<span class="material-symbols" aria-hidden="true">mail</span>
<span>hey@anarusso.com</span>
</a>
</address>
</aside>
</article>
Expanding card
Use an <article class="expanding-card"> with a direct
<dialog class="expanding-card-dialog">. The card
performs a shared-container transition from a compact feed item into
a full-screen modal detail surface. Keep the
same hero image in both states so the media appears to grow with the
container. ExpandingCard.Init() and
AutoInit() wire up the modal, measured clip origin,
back action, focus return, Escape, and reduced motion.
The container morph and the hero run on the slow spatial spring. The
close button and detail content run on the default spatial spring,
and their fade on the fast effects spring. So
motion="expressive" reaches all three. To change container
timing, override --md-comp-expanding-card-motion-duration
and --md-comp-expanding-card-motion-easing together. A
spring easing over another duration is no longer that spring.
Detail content starts after
--md-comp-expanding-card-content-delay, which reads
--md-sys-motion-duration-short4 and defaults to 200ms.
Override the system token on the page, or set the component property
on the article to change this card's delay. A value of
0ms removes the delay. Reduced motion disables the
transitions even with a nonzero delay override.
Closing waits for the actual clip transition, including cancellation.
Zero duration and reduced motion close immediately. Reopening or
destroying the instance invalidates pending close cleanup.
ExpandingCard.init(el, options) takes two callbacks, both
called with the card. onOpen runs once the dialog is
open and focus is on the back button. onClose runs
when closing starts.
Listen to Glass Souls
From your recent favorites
<article class="outlined expanding-card">
<figure>
<img src="images/glass-souls.jpg" alt="Pastel balloons floating above flowers">
<button type="button" class="expanding-card-trigger"
aria-label="Open Glass Souls album" aria-haspopup="dialog"></button>
</figure>
<header class="expanding-card-summary">
<h3>Listen to Glass Souls</h3>
<p class="subhead">From your recent favorites</p>
</header>
<dialog id="glass-souls-card" class="expanding-card-dialog"
aria-labelledby="glass-souls-title">
<button type="button" class="expanding-card-close" aria-label="Back">
<span class="material-symbols" aria-hidden="true">arrow_back</span>
</button>
<figure class="expanding-card-hero">
<img src="images/glass-souls.jpg" alt="Pastel balloons floating above flowers">
</figure>
<div class="expanding-card-content">
<header class="expanding-card-detail-header">
<h2 id="glass-souls-title">Glass Souls’ Biggest Hits</h2>
<div class="expanding-card-actions">
<button type="button" class="expanding-card-favorite" aria-label="Favorite album">
<span class="material-symbols" aria-hidden="true">favorite</span>
</button>
<button type="button" class="expanding-card-play" aria-label="Play album">
<span class="material-symbols" aria-hidden="true">play_arrow</span>
</button>
</div>
</header>
<div class="expanding-card-track">
<strong>Fragile</strong><small>Glass Souls</small>
<time datetime="PT3M34S">3:34</time>
</div>
</div>
</dialog>
</article>
Sizes
small, medium, and large
lock the height at 300px, 400px, and 500px, so a row of cards lines
up. Media takes the top 60%, and the action row sticks to the bottom.
These sizes are optional layout helpers and are not part of the M3
spec.
Small
The small card is 300px tall.
<article class="small">…</article>
<article class="medium">…</article>
<article class="large">…</article>