Containment
Cards
A surface for content and actions about one subject: elevated, filled or outlined, optionally interactive.
- COMPONENT_SPEC §19
- CSS · React
- Open in the playground
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.
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.
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.