Skip to main content

Card

A flexible surface container for related content and actions. Cards come in elevated, filled, and outlined variants and support structured slots for headers, media, body content, and footer actions.

Quick start
import { Card } from "mates-ui";

Card({
  variant: "elevated",
  header: { title: "Card title", subtitle: "Supporting text" },
  body: "The main content of the card goes here.",
  actions: html`${Button("Action", { variant: "ghost" })}`,
});

Variants

import { html, renderApp } from 'mates';
import { Card, Text } from 'mates-ui';

const App = () => () => html`
<x-row gap="var(--md-space-4)" wrap align="stretch">
${Card({ variant: "elevated", body: html`<x-col gap="var(--md-space-2)">${Text("Elevated", { type: "title-sm" })}${Text("Drop shadow, no border.", { type: "body-sm", color: "muted" })}</x-col>` })}
${Card({ variant: "filled", body: html`<x-col gap="var(--md-space-2)">${Text("Filled", { type: "title-sm" })}${Text("Tonal background, no shadow.", { type: "body-sm", color: "muted" })}</x-col>` })}
${Card({ variant: "outlined", body: html`<x-col gap="var(--md-space-2)">${Text("Outlined", { type: "title-sm" })}${Text("Border stroke, no fill.", { type: "body-sm", color: "muted" })}</x-col>` })}
</x-row>
`;

renderApp(App, document.getElementById('app'));

With header

import { html, renderApp } from 'mates';
import { Card } from 'mates-ui';
import { FolderIcon } from 'mates-icons';

const App = () => () => html`
<div style="max-width:22rem;width:100%;">
${Card({
variant: "elevated",
header: {
icon: FolderIcon,
title: "Project Assets",
subtitle: "Last modified 2 hours ago",
},
body: "Store and organise all media, documents and exports for this project in one place.",
})}
</div>
`;

renderApp(App, document.getElementById('app'));

With media

import { html, renderApp } from 'mates';
import { Card, FilledButton, OutlinedButton } from 'mates-ui';

const App = () => () => html`
<div style="max-width:24rem;width:100%;">
${Card({
variant: "elevated",
media: { src: "https://picsum.photos/seed/card/400/200", alt: "Scenic landscape", height: "12rem", position: "top", fit: "cover" },
header: { title: "Mountain Escape", subtitle: "Nature · Photography" },
body: "Breathtaking views from 3 000 m above sea level. Pack light and leave early.",
actions: html`
<x-row gap="var(--md-space-2)">
${FilledButton("Book now")}
${OutlinedButton("Save")}
</x-row>
`,
})}
</div>
`;

renderApp(App, document.getElementById('app'));

Interactive

import { html, atom, renderApp } from 'mates';
import { Card, Text } from 'mates-ui';

const App = () => {
const clicks = atom(0);
return () => html`
<x-row gap="var(--md-space-4)" wrap align="stretch">
${Card({
variant: "elevated", interactive: true, enableRipple: true, enableBloom: false,
ariaLabel: "Ripple card",
on: { click: () => clicks.set((n) => n + 1) },
body: html`<x-col gap="var(--md-space-2)">
${Text("Ripple press", { type: "title-sm" })}
${Text("Clicked " + clicks() + " time" + (clicks() === 1 ? "" : "s"), { type: "body-sm", color: "muted" })}
</x-col>`,
})}
${Card({
variant: "outlined", interactive: true, enableRipple: false, enableBloom: true,
ariaLabel: "Bloom card",
on: { click: () => clicks.set((n) => n + 1) },
body: html`<x-col gap="var(--md-space-2)">
${Text("Bloom press", { type: "title-sm" })}
${Text("Clicked " + clicks() + " time" + (clicks() === 1 ? "" : "s"), { type: "body-sm", color: "muted" })}
</x-col>`,
})}
</x-row>
`;
};

renderApp(App, document.getElementById('app'));

Full composition

import { html, renderApp } from 'mates';
import { Card, FilledButton, IconButton, TextButton } from 'mates-ui';
import { FolderIcon, MoreVertIcon } from 'mates-icons';

const App = () => () => html`
<div style="max-width:24rem;width:100%;">
${Card({
variant: "elevated",
header: {
icon: FolderIcon,
title: "Design System",
subtitle: "Mates UI · v2.0",
action: html`${IconButton(MoreVertIcon, { ariaLabel: "More options", variant: "ghost" })}`,
},
media: { src: "https://picsum.photos/seed/card/400/200", alt: "Cover", height: "11rem", position: "middle", fit: "cover" },
body: "A comprehensive component library built on Mates. Consistent tokens, accessible markup, and zero runtime overhead.",
actions: html`
<x-row gap="var(--md-space-2)">
${FilledButton("Get started")}
${TextButton("View source")}
</x-row>
`,
})}
</div>
`;

renderApp(App, document.getElementById('app'));

Card

import { Card } from "mates-ui";
Prop Type Default Description
actions TemplateResult Footer action bar — typically a row of Buttons.
ariaLabel string Accessible label for interactive cards that have no visible title.
attr AttrMap HTML attributes on the root element.
body TemplateResult | string Main card content.
classes string | string[] Extra root classes — joined and appended after built-in classes.
data DataAttrMap Root `data-*` map (`testId` → `data-testid`). `testId` prop / `attr` win over `data`.
enableBloom boolean true Soft bloom press effect for interactive cards. Used when enableRipple is false.
enableRipple boolean true Material ripple on press for interactive cards. Set false to use bloom instead.
header CardHeaderOptions | TemplateResult Structured header with icon/avatar, title, subtitle, and an optional trailing action. Pass a raw TemplateResult for full control.
href string When set together with interactive, the card renders as an <a> element.
id string Root `id` shorthand (`attr.id` wins). Form native-control `id` props are unchanged.
interactive boolean false Renders the card as a button (or anchor when href is set) with press feedback.
media CardMediaOptions | TemplateResult Top, middle, or bottom image. Structured { src, alt, height, fit } or a raw TemplateResult.
on OnEventMap DOM event handlers on the root element.
style StyleMap Inline CSS on the root element.
variant "elevated" | "filled" | "outlined" "elevated" Visual style of the card surface. Elevated adds a drop shadow, filled uses a tonal background, outlined draws a border.