Skip to content

Card

Hero example

Card body

When to use

Reach for Card whenever you need an elevated, opaque container for a block of content — a dashboard tile, a settings panel, an inline domain object that isn't one of the specialised InlineCards. Card is the default container: every page in the dashboard composes from Card and Section, and most surfaces in apps/web use it as well.

Don't reach for Card when the surface needs to feel translucent or refractive — that's Glass. Don't use it for full-screen overlays — that's Sheet. For domain objects with specific data shapes (filings, grants, board consents), use the matching InlineCard variant — Card is the generic fallback.

Do — render Card as the container for any block of content that needs visual elevation against the page background.
Don't — nest Card inside Card. Use Section for grouping content blocks; Card-in-Card breaks the elevation hierarchy.

Anatomy

A single <div class="card"> with no internal structure. The padding prop applies via inline style and accepts any CSS length (number → px). All HTMLAttributes<HTMLDivElement> pass through via spread.

The visual identity comes from .card in @matter/components/styles.css:

  • background: var(--bg-elev)
  • border: 1px solid var(--border-soft)
  • border-radius: var(--radius-lg)

There are no slots — Card is a fully transparent container.

Props

PropTypeDefaultDescription
paddingnumber | string32
classNamestring
styleReact.CSSProperties
childrenReact.ReactNode

Also accepts Omit<React.HTMLAttributes<HTMLDivElement>, "children">.

States

Card itself has no states — it's a static container. If you need hover or active states, wrap Card in an interactive element or apply :hover styles via the consumer's CSS using a class composition pattern (e.g. <Card className="card-clickable">).

Density

Card body
Card body

In compact, the default padding drops from 32 to 24 (if not overridden). The radius and border stay constant.

Themes

Card body
Card body
Card body

Tokens consumed

colors24 tokensv2.3.0
#FFFFFF
matter
src ↗
#F7F6F3
matter
src ↗
#FFFFFF
matter
src ↗
#fdeee7
matter
src ↗
#e5ece6
matter
src ↗
#eae9ee
matter
src ↗
#fdeee7
brand
src ↗
rgba(255, 178, 138, 0.1)
brand
src ↗
#e5ece6
brand
src ↗
#f2f5f2
brand
src ↗
#eae9ee
brand
src ↗
#f8f0f0
brand
src ↗
rgba(42, 111, 219, 0.1)
brand
src ↗
rgba(31, 138, 91, 0.1)
brand
src ↗
rgba(180, 90, 26, 0.1)
brand
src ↗
rgba(180, 90, 26, 0.1)
brand
src ↗
rgba(176, 50, 43, 0.1)
brand
src ↗
rgba(31, 138, 91, 0.08)
brand
src ↗
rgba(42, 111, 219, 0.08)
brand
src ↗
rgba(176, 50, 43, 0.08)
brand
src ↗
rgba(31, 138, 91, 0.1)
brand
src ↗
rgba(107, 114, 128, 0.1)
brand
src ↗
rgba(176, 50, 43, 0.1)
brand
src ↗
#fdeee7
brand
src ↗
colors6 tokensv2.3.0
#F0A074
matter
src ↗
rgba(255, 178, 138, 0.25)
brand
src ↗
rgba(0, 0, 0, 0.06)
brand
src ↗
#e7e5e4
brand
src ↗
#d6d3d1
brand
src ↗
rgba(229, 229, 229, 0.64)
brand
src ↗
radii15 tokensv2.3.0
10
matter
src ↗
16
matter
src ↗
24
matter
src ↗
32
matter
src ↗
72
matter
src ↗
999
matter
src ↗
6
brand
src ↗
10
brand
src ↗
12
brand
src ↗
16
brand
src ↗
20
brand
src ↗
24
brand
src ↗
32
brand
src ↗
72
brand
src ↗
9999
brand
src ↗

Background reads --bg-elev. Border reads --border-soft. Radius reads --radius-lg.

Accessibility

  • Keyboard interactions. None — Card is non-interactive. Children carry their own contracts.
  • ARIA roles and properties. Generic container, no role override. If Card represents a named region with a heading, wrap the heading in an <h2> and set aria-labelledby on the Card pointing to that heading's id — or render the surrounding element as a <section> and let it carry the semantics.
  • Focus order. No internal focus.
  • Screen-reader expectations. Children announce in DOM order. No prefix or suffix from the Card itself.
  • Reduced motion. N/A — no motion.
  • Forced colors. Background → Canvas; border → ButtonBorder. Radius preserved.
  • WIG rules. No specific WIG implications — Card is passive. Consumers wrap interactive children with the right WIG contracts.

Do / Don't

Do — pass padding={48} for hero-level cards and padding={16} for inline summary cards. The default 32 is the canonical middle ground.
Don't — set padding via inline style. Use the prop so the value lands in the right place in the cascade.
Do — wrap multiple related cards in a Section to share rhythm and spacing.
Don't — nest Card inside Card. It breaks the elevation contract — Card is the elevation layer.
Do — attach role="region" and aria-labelledby to Card when it represents a named section. Headings inside need a heading element.
Don't — use Card as a <section> substitute without setting role. Semantic HTML beats generic divs.

Recipes

This component appears in every recipe under /recipes. Card is the default container.

Code example

import { Card } from "@matter/components";

export function RecentActivityCard({ events }: { events: AuditEvent[] }) {
  return (
    <Card aria-labelledby="recent-activity-h2">
      <h2 id="recent-activity-h2">Recent activity</h2>
      <ul>
        {events.map((event) => (
          <li key={event.id}>{event.summary}</li>
        ))}
      </ul>
    </Card>
  );
}

Source

packages/components/src/Card

On this page