/Documentation

Card

PublishedUpdated

A card groups the content and actions about one subject on one surface: a title, media, supporting text and buttons. Use cards when each item needs its own title, media and controls, and should read as a separate object. M3 has three: elevated, filled and outlined. See the M3 card guidelines.

Usage

The card places its sections in M3 order: media, header, content, then actions.

Vanilla
import { createCard } from 'mtrl';

const card = createCard({
  header: { title: 'The Kiss', subtitle: 'Gustav Klimt, 1908' },
  content: { text: 'A couple embracing, wrapped in gold leaf and ornament.' },
  buttons: [{ text: 'Details', variant: 'text' }],
});
document.body.append(card.element);
Web Components
<m-card headline="The Kiss" subhead="Gustav Klimt, 1908">
  A couple embracing, wrapped in gold leaf and ornament.
  <m-button slot="actions" variant="text">Details</m-button>
</m-card>
React
import { Card, Button } from 'mtrl/react';

export function Example() {
  return (
    <Card headline="The Kiss" subhead="Gustav Klimt, 1908">
      A couple embracing, wrapped in gold leaf and ornament.
      <Button slot="actions" variant="text">Details</Button>
    </Card>
  );
}
Vue
<script setup lang="ts">
import { MCard, MButton } from 'mtrl/vue';
</script>

<template>
  <MCard headline="The Kiss" subhead="Gustav Klimt, 1908">
    A couple embracing, wrapped in gold leaf and ornament.
    <template #actions>
      <MButton variant="text">Details</MButton>
    </template>
  </MCard>
</template>
Svelte
<script lang="ts">
  import { Card, Button } from 'mtrl/svelte';
</script>

<Card headline="The Kiss" subhead="Gustav Klimt, 1908">
  A couple embracing, wrapped in gold leaf and ornament.
  {#snippet actions()}
    <Button variant="text">Details</Button>
  {/snippet}
</Card>
SolidJS
import { Card, Button } from 'mtrl/solid';

export function Example() {
  return (
    <Card headline="The Kiss" subhead="Gustav Klimt, 1908">
      A couple embracing, wrapped in gold leaf and ornament.
      <Button slot="actions" variant="text">Details</Button>
    </Card>
  );
}

Examples

Media

media puts an image at the top, clipped to the card's shape. Its alt is required when the image carries meaning, and '' when it does not. In the factory, aspectRatio ('16:9', '4:3', '1:1') sets its crop, and contain: true fits it whole.

Vanilla
import { createCard } from 'mtrl';

const card = createCard({
  variant: 'filled',
  media: {
    src: '/assets/playground/landscape-1.svg',
    alt: 'A mountain landscape',
  },
  header: { title: 'Highlands', subtitle: 'Three days on foot' },
});
document.body.append(card.element);
Web Components
<m-card variant="filled" headline="Highlands" subhead="Three days on foot">
  <img slot="media" src="/assets/playground/landscape-1.svg" alt="A mountain landscape">
</m-card>
React
import { Card } from 'mtrl/react';

export function Example() {
  return (
    <Card variant="filled" headline="Highlands" subhead="Three days on foot">
      <img slot="media" src="/assets/playground/landscape-1.svg" alt="A mountain landscape" />
    </Card>
  );
}
Vue
<script setup lang="ts">
import { MCard } from 'mtrl/vue';
</script>

<template>
  <MCard variant="filled" headline="Highlands" subhead="Three days on foot">
    <template #media>
      <img src="/assets/playground/landscape-1.svg" alt="A mountain landscape" />
    </template>
  </MCard>
</template>
Svelte
<script lang="ts">
  import { Card } from 'mtrl/svelte';
</script>

<Card variant="filled" headline="Highlands" subhead="Three days on foot">
  {#snippet media()}
    <img src="/assets/playground/landscape-1.svg" alt="A mountain landscape" />
  {/snippet}
</Card>
SolidJS
import { Card } from 'mtrl/solid';

export function Example() {
  return (
    <Card variant="filled" headline="Highlands" subhead="Three days on foot">
      <img slot="media" src="/assets/playground/landscape-1.svg" alt="A mountain landscape" />
    </Card>
  );
}

Clickable

clickable makes the whole card a button: it is in the tab order, Enter and Space click it, and a press shows the ripple. Keep other controls out of a clickable card.

Vanilla
import { createCard } from 'mtrl';

const card = createCard({
  variant: 'outlined',
  clickable: true,
  header: { title: 'Unsaved changes' },
  content: { text: 'Your edits have not been published yet.' },
});
document.body.append(card.element);
Web Components
<m-card variant="outlined" clickable headline="Unsaved changes">Your edits have not been published yet.</m-card>
React
import { Card } from 'mtrl/react';

export function Example() {
  return (
    <Card variant="outlined" clickable headline="Unsaved changes">Your edits have not been published yet.</Card>
  );
}
Vue
<script setup lang="ts">
import { MCard } from 'mtrl/vue';
</script>

<template>
  <MCard variant="outlined" clickable headline="Unsaved changes">Your edits have not been published yet.</MCard>
</template>
Svelte
<script lang="ts">
  import { Card } from 'mtrl/svelte';
</script>

<Card variant="outlined" clickable headline="Unsaved changes">Your edits have not been published yet.</Card>
SolidJS
import { Card } from 'mtrl/solid';

export function Example() {
  return (
    <Card variant="outlined" clickable headline="Unsaved changes">Your edits have not been published yet.</Card>
  );
}

A card has no width of its own: it fills the column, grid cell or flex track it is in, as the M3 specs describe. The --small, --medium and --large classes fix it at 344, 480 and 624dp. The factory's content helpers, createCardHeader(), createCardContent(), createCardMedia() and createCardActions(), build sections for setHeader(), addContent(), addMedia() and setActions(). The web component takes its sections in slots: media, avatar, headline, subhead, header-action, the content, and actions.

API

Options

Option Type Default Description
variant 'elevated' | 'filled' | 'outlined' 'elevated' Card variant
clickable boolean false A button: ripple, Enter and Space, and the click event
interactive boolean false Hover and press states only: no role, no tab stop, no key handling
fullWidth boolean false width: 100%
draggable boolean false HTML drag, with elevation while dragged and dragstart / dragend
header CardHeaderConfig undefined { title, subtitle, avatar, action, class }
content CardContentConfig undefined { text, html, children, padding, class }; html wins over text, padding is on by default
media CardMediaConfig undefined { src, alt, element, aspectRatio, contain, position, class }; position is 'top' or 'bottom'
actions CardActionsConfig undefined { actions, align, fullBleed, vertical, class }; align is 'start', 'center', 'end' or 'space-between'
buttons ButtonConfig[] undefined An actions row of buttons, added a microtask after creation
aria CardAriaAttributes undefined { role, label, labelledby, describedby }, each written as an aria- attribute; role defaults to article, or button when clickable
class string undefined Additional CSS classes
prefix string 'mtrl' Prefix for CSS class names

headerConfig, contentConfig, mediaConfig and actionsConfig are the long forms of header, content, media and actions.

Methods

Method Returns Description
setHeader(element) CardComponent Replaces the header; after the media when there is some, otherwise first
addContent(element) CardComponent Appends a content section; one without the mtrl-card-content class is ignored
addMedia(element, position?) CardComponent Inserts media at the top (default) or the bottom
setActions(element) CardComponent Replaces the actions row, last
makeDraggable(onDragStart?) CardComponent Makes the card draggable, keeping aria-grabbed in step
focus() CardComponent Focuses the card
destroy() void Removes the card and its listeners
Helper Returns Description
createCardHeader(config) HTMLElement A header from CardHeaderConfig
createCardContent(config) HTMLElement A content section from CardContentConfig
createCardMedia(config) HTMLElement Media from CardMediaConfig
createCardActions(config) HTMLElement An actions row from CardActionsConfig

Events

Event Payload Description
click the DOM event A clickable card was clicked, or activated with Enter or Space
dragstart / dragend { event } A draggable card's drag began or ended

The web component's activation is the native click.

Accessibility

  • The card is an article, or a button with a tab stop that answers Enter and Space when it is clickable. interactive alone changes only the hover and press states: the card keeps the article role and takes no focus.
  • The headline is an h3 and names the article; aria.label or aria.labelledby overrides it. The subtitle is a paragraph, and the header and content carry no role of their own.
  • makeDraggable() keeps aria-grabbed in step during a drag; draggable: true does not set it.
  • A clickable card is one control: buttons inside it are not reachable as their own.

Styling

The elevation follows the variant, and rises while the card is hovered or dragged.

.mtrl-card { }
.mtrl-card--elevated, .mtrl-card--filled, .mtrl-card--outlined { }
.mtrl-card--interactive, .mtrl-card--focused, .mtrl-card--dragging, .mtrl-card--full-width { }
.mtrl-card--small, .mtrl-card--medium, .mtrl-card--large { }
.mtrl-card__header, .mtrl-card__header-text, .mtrl-card__header-title, .mtrl-card__header-subtitle { }
.mtrl-card__header-avatar, .mtrl-card__header-action { }
.mtrl-card__media, .mtrl-card__content, .mtrl-card__actions { }

Measurements

Attribute Value Token
Container corner 12dp ContainerShape (CornerMedium), all three variants
Elevated container surface-container-low ElevatedCardTokens.ContainerColor
Filled container surface-container-highest FilledCardTokens.ContainerColor
Outlined container surface OutlinedCardTokens.ContainerColor
Outline 1dp outline-variant; on-surface focused OutlinedCardTokens.OutlineColor / OutlineWidth / FocusOutlineColor
Elevated elevation Level 1, level 2 hovered ContainerElevation / HoverContainerElevation
Filled and outlined elevation Level 0, level 1 hovered HoverContainerElevation
Dragged elevation Level 4 elevated, level 3 otherwise DraggedContainerElevation