Skip to content

Containment

Cards

A surface for content and actions about one subject: elevated, filled or outlined, optionally interactive.

Examples

Elevated, filled and outlined

Pick by how much the card should stand out from its background.

Elevated

Content and actions about one subject.

Filled

Content and actions about one subject.

Outlined

Content and actions about one subject.

React HTML
import { Card } from '@kyralabs/m3exp-web/react';

const VARIANTS = [
  ['elevated', 'Elevated'],
  ['filled', 'Filled'],
  ['outlined', 'Outlined'],
] as const;

export default function Variants() {
  return (
    <>
      {VARIANTS.map(([variant, title]) => (
        <Card
          key={variant}
          variant={variant}
          as="article"
          style={{ padding: 'var(--m3e-sys-space-200)' }}
        >
          <h4>{title}</h4>
          <p>Content and actions about one subject.</p>
        </Card>
      ))}
    </>
  );
}
<article class="m3e-card m3e-card--elevated" style="padding: var(--m3e-sys-space-200)">
  <h4>Elevated</h4>
  <p>Content and actions about one subject.</p>
</article>
<article class="m3e-card m3e-card--filled" style="padding: var(--m3e-sys-space-200)">
  <h4>Filled</h4>
  <p>Content and actions about one subject.</p>
</article>
<article class="m3e-card m3e-card--outlined" style="padding: var(--m3e-sys-space-200)">
  <h4>Outlined</h4>
  <p>Content and actions about one subject.</p>
</article>

Run the HTML version (CSS classes only).

Interactive card

One CardAction makes the whole card its hit area, with the state layer, elevation change and focus ring on the card.

Glacier hike

The whole card is the link. Other controls stay clickable above it.

React HTML
import { Button, Card, CardAction } from '@kyralabs/m3exp-web/react';

export default function Interactive() {
  return (
    <Card variant="outlined" as="article" style={{ padding: 'var(--m3e-sys-space-200)' }}>
      <h4>
        <CardAction href="/components/card/">Glacier hike</CardAction>
      </h4>
      <p>The whole card is the link. Other controls stay clickable above it.</p>
      <Button variant="tonal" size="xs">
        Save
      </Button>
    </Card>
  );
}
<article
  class="m3e-card m3e-card--outlined m3e-card--interactive"
  style="padding: var(--m3e-sys-space-200)"
>
  <h4><a class="m3e-card__action" href="/components/card/">Glacier hike</a></h4>
  <p>The whole card is the link. Other controls stay clickable above it.</p>
  <button type="button" class="m3e-button m3e-button--tonal m3e-button--size-xs">Save</button>
</article>

Run the HTML version (CSS classes only).

API

React

<Card>

Card (COMPONENT_SPEC §19): the .m3e-card classes. Add one CardAction to make it interactive: its hit area stretches over the card, which then shows the state layer, the elevation change and the focus ring.

Prop Type Default Description
variant "elevated" | "filled" | "outlined" filled Default filled (Compose Card).
as 'div' | 'article' | 'section' | 'li' div Element to render. Default div; use article for self-contained content.
children ReactNode

Also accepts HTML attributes not listed here.

<CardAction>

The primary action of an interactive Card (.m3e-card__action).

Prop Type Default Description
href string Navigates (renders <a>); otherwise a <button>.
isDisabled boolean
children ReactNode
onPress (e: PressEvent) => void

Also accepts HTML attributes not listed here.

CSS

card.css

@import '@kyralabs/m3e-css/card.css';

Cards: filled (default), elevated, outlined (COMPONENT_SPEC §19). No JavaScript needed.

<article class="m3e-card m3e-card--elevated">…</article>

Interactive card: one primary action whose hit area stretches over the card; the card shows the state layer, elevation change and focus ring. Other controls inside stay clickable above it.

<article class="m3e-card m3e-card--filled m3e-card--interactive">
  <h3><a class="m3e-card__action" href="/place">Title</a></h3>
  …
</article>

A disabled interactive card has aria-disabled="true" on its action.

33 custom properties it reads
Custom property Default
--m3e-comp-elevated-card-container-color var(--m3e-sys-color-surface-container-low)
--m3e-comp-elevated-card-container-color-disabled var(--m3e-sys-color-surface)
--m3e-comp-elevated-card-container-opacity-disabled 0.38
--m3e-comp-elevated-card-container-shadow var(--m3e-sys-elevation-level1-shadow)
--m3e-comp-elevated-card-container-shadow-disabled var(--m3e-sys-elevation-level1-shadow)
--m3e-comp-elevated-card-container-shadow-dragged var(--m3e-sys-elevation-level4-shadow)
--m3e-comp-elevated-card-container-shadow-focus var(--m3e-sys-elevation-level1-shadow)
--m3e-comp-elevated-card-container-shadow-hover var(--m3e-sys-elevation-level2-shadow)
--m3e-comp-elevated-card-container-shadow-pressed var(--m3e-sys-elevation-level1-shadow)
--m3e-comp-elevated-card-container-shape var(--m3e-sys-shape-corner-medium)
--m3e-comp-elevation-incoming-duration 120ms
--m3e-comp-elevation-incoming-easing cubic-bezier(0.4, 0, 0.2, 1)
--m3e-comp-elevation-outgoing-duration-hovered 120ms
--m3e-comp-elevation-outgoing-easing cubic-bezier(0.4, 0, 0.6, 1)
--m3e-comp-filled-card-container-color var(--m3e-sys-color-surface-container-highest)
--m3e-comp-filled-card-container-color-disabled var(--m3e-sys-color-surface-variant)
--m3e-comp-filled-card-container-opacity-disabled 0.38
--m3e-comp-filled-card-container-shadow var(--m3e-sys-elevation-level0-shadow)
--m3e-comp-filled-card-container-shadow-disabled var(--m3e-sys-elevation-level0-shadow)
--m3e-comp-filled-card-container-shadow-dragged var(--m3e-sys-elevation-level3-shadow)
--m3e-comp-filled-card-container-shadow-focus var(--m3e-sys-elevation-level0-shadow)
--m3e-comp-filled-card-container-shadow-hover var(--m3e-sys-elevation-level1-shadow)
--m3e-comp-filled-card-container-shadow-pressed var(--m3e-sys-elevation-level0-shadow)
--m3e-comp-filled-card-container-shape var(--m3e-sys-shape-corner-medium)
--m3e-comp-outlined-card-container-color var(--m3e-sys-color-surface)
--m3e-comp-outlined-card-container-shadow var(--m3e-sys-elevation-level0-shadow)
--m3e-comp-outlined-card-container-shadow-disabled var(--m3e-sys-elevation-level0-shadow)
--m3e-comp-outlined-card-container-shadow-dragged var(--m3e-sys-elevation-level3-shadow)
--m3e-comp-outlined-card-container-shape var(--m3e-sys-shape-corner-medium)
--m3e-comp-outlined-card-outline-color var(--m3e-sys-color-outline-variant)
--m3e-comp-outlined-card-outline-color-disabled var(--m3e-sys-color-outline)
--m3e-comp-outlined-card-outline-opacity-disabled 0.12
--m3e-comp-outlined-card-outline-width 1px

Accessibility

non-interactive by default; interactive cards have one primary action (.m3e-card__action, React <CardAction>) whose hit area stretches over the card; the card shows the state layer and focus ring; secondary controls stay clickable above it. Disabled = the action is aria-disabled.

Every interactive part has a target of at least 48 × 48 px, a visible focus ring for keyboard focus only, and works under forced-colors and prefers-reduced-motion.

Sources

Values come from androidx Compose Material3 1.5.0-beta01 at 19991f2c and from m3.material.io, as cited below from COMPONENT_SPEC §19 and the m3e-css sources.

{Filled,Elevated,Outlined}CardTokens.kt@19991f2c via CardDefaults: shape medium; filled surface-container-highest; elevated surface-container-low, Level1 (hover Level2); outlined surface + 1dp outline-variant, no hover elevation (outlinedCardElevation); content on-surface. Elevation animates with the ElevationDefaults tweens (incoming 120ms FastOutSlowIn, outgoing 150/120ms).

card.css sources

(compose/material3 @ 19991f2c):

  • {Filled,Elevated,Outlined}CardTokens via CardDefaults (Card.kt): shape, container color, elevation per state. Outlined cards keep their default elevation when pressed, focused or hovered (CardDefaults.outlinedCardElevation), unlike OutlinedCardTokens.
  • Content color: contentColorFor(container) = onSurface for all three (ColorScheme.kt L1108-1116).
  • Disabled: container = DisabledContainerColor @ DisabledContainerOpacity over the container; content @ 0.38 (DisabledAlpha); outlined border = DisabledOutlineColor @ DisabledOutlineOpacity over ElevatedCardTokens.ContainerColor (CardDefaults.outlinedCardBorder).
  • Elevation animates with ElevationDefaults tweens (elevation-motion.css): incoming when a state starts, outgoing (the hovered variant, the most frequent on the web) when it ends.
  • Compose clips content to the shape: overflow is clipped.
  • Compose's border modifier draws inside the bounds without affecting layout, so the outline is drawn by ::after (an inset overlay) instead of a CSS border that would shrink the content box.