Cards

Cards

Material Design 3 cards, from the HTML.

An <article> is an elevated card. Any heading is the headline, <p class="subhead"> is the optional subhead, <p class="supporting-text"> is supporting copy, direct <img>, <picture>, or <figure> is media, and 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; an interactive card rises to 2 on hover. The headline is title-medium / on-surface, the subhead is title-small / on-surface, and supporting text is body-medium / on-surface-variant. Inset is 16dp.

Weekend in the mountains

Three-day itinerary

Explore trails, overlooks, and places to rest along the way.

Mountain valley beneath a cloudy sky
<article>
  <header>
    <h3>Weekend in the mountains</h3>
    <p class="subhead">Three-day itinerary</p>
  </header>
  <p class="supporting-text">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

Default is elevated. filled uses surface-container-highest at rest (no shadow). outlined (or border) draws 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

A static card has no hover treatment. 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 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.

<article>
  <a class="primary-action" href="/reservation/42">
    <h3>Upcoming reservation</h3>
    <p>Open the reservation details.</p>
  </a>
</article>

Collections

card-collection creates 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. Change the minimum card width or replace the complete grid track token when the content needs a different column strategy.

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. Add carousel-item to every card; AutoInit() supplies the M3 scroll track.

<!-- 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. Wrap it in a <figure> if you want a caption on the image. The <figcaption> is an opaque, rounded bounding shape using the paired surface and on-surface roles, so image colors cannot reduce the contrast of its text or icons. Normal text must retain at least 4.5:1 contrast; large text and meaningful icons require at least 3:1. Recheck those ratios if you override either color token.

Card title

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>

Horizontal

The same card can use two orientations without changing its content order. Add horizontal to move the media to the start and stack the headline, supporting text, and action beside it. A direct .primary-action link can wrap those same slots when the whole horizontal card is one destination. Without a size helper, its height follows content with a 240px minimum; small, medium, and large provide fixed expanded heights. Below 600px, both forms return to the vertical layout and fixed heights reset to content.

Musician playing guitar during a live performance

Performances at The Hideout

Watch exclusive live performances at The Hideout every Saturday starting at 7pm.

Musician playing guitar during a live performance

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

An <aside> expands in normal flow below the persistent media, headline, and subhead. Place a .card-reveal-trigger button over the media; the same button opens and closes the details. Cards.Init() (and AutoInit() when the card contains both that button and one identified direct <aside>) owns aria-expanded and applies the closed-panel styles only after the disclosure contract is accepted. The trigger must be enabled when Cards initializes; rejected, disabled-only, unidentified, or multi-panel disclosures leave 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. The reveal grows the card instead of covering or internally scrolling it. This replaces legacy .activator markup and heading-based close controls. Use the same native button to open and close the panel.

<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

An expanding card performs a shared-container transition from a compact feed item into a full-screen modal detail surface. Use an <article class="expanding-card"> with a direct <dialog class="expanding-card-dialog">. 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.

Override --md-comp-expanding-card-motion-duration to change container timing. 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. The onClose callback runs when closing starts.

Plants in patterned vases against a blue wall

Listen to Glass Souls

From your recent favorites

Plants in patterned vases against a blue wall

Glass Souls’ Biggest Hits

Fragile Glass Souls
<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%; the action row sticks to the bottom. These sizes are not in the M3 spec — they are optional layout helpers.

Small

The small card is 300px tall.

<article class="small">…</article>
<article class="medium">…</article>
<article class="large">…</article>
  • Source color

    The seed every generated ramp derives from. Pick one and browse the docs — the whole theme follows. Error does not: it is a fixed hue.