Pular para o conteúdo

Cards

components specs/components/cards.kmd

Card — a container of related content + actions, typically clickable as a whole. Material parity (`/components/cards`). 3 variants (elevated, filled, outlined), uniform anatomy, and rules for composition with images, lists, and feeds.

Quando esta spec se aplica

Triggers primários

Todos os triggers

Corpo da especificação

Spec — Cards

Facet Visual do Koder Design. Material parity: https://m3.material.io/components/cards.

3 card variants

VariantVisualUse
ElevatedSurface bg + shadow level 1Default — slight depth, draws attention
FilledSurface-variant bg + no shadowLess emphasis; for embedded cards within a denser context
OutlinedTransparent bg + 1px accent borderHigh structure, low visual weight (forms with grouped fields)

Anatomy

┌──────────────────────────────┐
│  [Optional media]            │   ← image / video / illustration
│                              │
│  Title (title-large)         │
│  Subtitle (body-medium muted)│
│                              │
│  Body content                │
│                              │
│  [Action 1] [Action 2]       │   ← optional actions
└──────────────────────────────┘
  • Container: radius-md, padding 16-24 px
  • Media (optional): full-width image, aspect 16:9 default
  • Title: title-large (22/28, weight 600)
  • Subtitle: body-medium (14/20) in text-muted
  • Body: body-medium
  • Actions: button row, right-aligned (or left in RTL)

R1 — Card density

DensityPaddingUse
Comfortable24 pxDefault — feeds, dashboards
Compact16 pxDense lists (table cards, multi-column grids)
Spacious32 pxHero cards (single card per fold)

Density follows themes/ui-style.kmd preset's density token; can be explicitly overridden per card via class modifier.

R2 — Clickable card

Most cards are clickable as a whole (navigate to detail).

  • Entire card surface is the hit area
  • Hover state: subtle elevation boost (+1 level) OR background tint
  • Focus ring: 2 px on outer container (per interaction/states.kmd)
  • role="link" if it navigates; role="button" if it triggers an action
  • Inner buttons are stop-propagation: clicking them doesn't trigger the card's main action
  • Cursor: pointer on whole card

R3 — Compositions

Feed item card

Used in vertical scrollable feeds (per canonical-layouts.kmd L1).

  • Compact density
  • Title + subtitle + 1-3 lines of body
  • Optional avatar (40 px) + author name + timestamp
  • Click navigates to detail

Product card

  • Elevated variant
  • Hero image (16:9 or 1:1)
  • Title + price + rating
  • "Add to cart" or similar primary action
  • Hover: shadow boost + slight scale (1.02)

List-detail card

  • Outlined or Filled variant
  • Selected state: accent-tinted bg + accent border (1.5 px)
  • Used in list-detail layout (per L2)

Stats card (dashboard)

  • Outlined variant
  • Large numeric value (headline-large)
  • Label (title-small muted)
  • Optional sparkline / mini-chart

R4 — Cards within cards

Anti-pattern. A card MUST NOT contain another card. If nesting feels needed, the inner content should be a list-item, expansion-panel, or embedded section — not a card.

Exception: a card containing one image-card preview (e.g., a tweet embed inside a notification card) — allowed because the inner is a quoted/embedded artifact, not a peer container.

R5 — Media handling

  • Aspect ratio: 16:9 (default), 1:1 (square avatar/product), 4:3 (legacy)
  • Loading: skeleton placeholder matches final aspect (no layout shift)
  • Failure: fallback to surface-variant bg + icon
  • Alt text: alt attribute REQUIRED for non-decorative images
  • Lazy-loading: loading="lazy" for cards below the fold

R6 — Per-preset variation

PresetVariant emphasis
material3Elevated default (subtle shadow)
material2Stronger shadows, sharper corners
windows_11Mica backdrop blur on Elevated
windows_953D bevel border instead of shadow; sharp corners
glassmorphismTranslucent + backdrop blur
brutalistOutlined only (no shadows, thick border)
neumorphismSoft inverse shadow (looks "pressed into" surface)
dieter_ramsOutlined default; minimal ornament

R7 — Accessibility

  • Card title is a heading (<h2>-<h4> depending on page level)
  • Clickable card focus ring on outer container only (not on title)
  • Inner buttons reachable by Tab (separately from card focus)
  • Reading order matches visual order (DOM = visual)

R8 — Forbidden patterns

  • ❌ Card without title (just a body of text) — use a <section> instead
  • ❌ Multiple primary actions in card actions row (1 primary, N secondary)
  • ❌ Card with no border + no shadow + no bg variation (use a plain <div>)
  • ❌ Animations that change card height on hover (causes feed shift)
  • app-layout/canonical-layouts.kmd — feed (L1) uses cards
  • themes/elevation.kmd — Elevated variant level 1 default
  • themes/shape.kmd — radius-md container
  • interaction/states.kmd — hover/focus per card type
  • foundations/elements.kmd — Container family

Requirements (testable)

Requirement: Card variants render their defining visual treatment {#req-cards-variants}

A card SHALL render according to its declared variant: the Elevated variant SHALL have a surface background with shadow level 1, the Filled variant SHALL have a surface-variant background and no shadow, and the Outlined variant SHALL have a transparent background with a 1px accent border.

Scenario: Elevated variant carries shadow level 1

  • GIVEN a card declared with the Elevated variant
  • WHEN the card is rendered
  • THEN its background resolves to the surface color
  • AND a shadow of elevation level 1 is applied to the container

Scenario: Filled variant has no shadow

  • GIVEN a card declared with the Filled variant
  • WHEN the card is rendered
  • THEN its background resolves to the surface-variant color
  • AND no box shadow is applied to the container

Scenario: Outlined variant has a transparent background and accent border

  • GIVEN a card declared with the Outlined variant
  • WHEN the card is rendered
  • THEN its background is transparent
  • AND a 1px border in the accent color is drawn around the container

Requirement: Card density maps to the specified padding {#req-cards-density}

A card SHALL apply container padding matching its density token: Comfortable SHALL be 24px, Compact SHALL be 16px, and Spacious SHALL be 32px, unless explicitly overridden per card via a class modifier.

Scenario: Compact density applies 16px padding

  • GIVEN a card set to Compact density
  • WHEN the card is rendered
  • THEN its container padding computes to 16px on all sides

Scenario: Per-card modifier overrides the preset density

  • GIVEN a card whose theme preset density resolves to Comfortable (24px)
  • WHEN the card carries a class modifier setting Spacious density
  • THEN its container padding computes to 32px on all sides

Requirement: Clickable card exposes the whole surface with correct role and inner-button isolation {#req-cards-clickable}

A clickable card SHALL make the entire card surface the hit area, SHALL expose role="link" when it navigates or role="button" when it triggers an action, and clicking an inner button SHALL NOT trigger the card's main action.

  • GIVEN a clickable card that navigates to a detail view
  • WHEN the user clicks a non-interactive region of the card surface
  • THEN the card's main navigation action fires
  • AND the card element exposes role="link"

Scenario: Inner button click does not trigger the card action

  • GIVEN a clickable card containing an inner action button
  • WHEN the user clicks the inner button
  • THEN the inner button's own handler fires
  • BUT the card's main action does not fire

Requirement: Card media requires alt text and reserves layout space {#req-cards-media}

Card media SHALL provide an alt attribute for every non-decorative image, SHALL show a skeleton placeholder matching the final aspect ratio while loading so no layout shift occurs, and SHALL fall back to a surface-variant background with an icon on load failure.

Scenario: Non-decorative media image carries alt text

  • GIVEN a card whose media is a non-decorative image
  • WHEN the card is rendered
  • THEN the image element has a non-empty alt attribute

Scenario: Loading placeholder reserves the final aspect ratio

  • GIVEN a card with 16:9 media that has not finished loading
  • WHEN the card is rendered
  • THEN a skeleton placeholder occupies a 16:9 box
  • AND the media box height does not change once the image loads (no layout shift)

Requirement: Card accessibility uses a heading title and a single outer focus ring {#req-cards-a11y}

A card SHALL render its title as a heading element (<h2><h4> per page level), a clickable card SHALL place the focus ring on the outer container only and not on the title, inner buttons SHALL remain reachable by Tab separately from the card focus, and the DOM reading order SHALL match the visual order.

Scenario: Card title is a heading element

  • GIVEN a card with a title
  • WHEN the card is rendered
  • THEN the title is a heading element in the <h2><h4> range

Scenario: Focus ring appears on the container, and inner buttons are separately tabbable

  • GIVEN a clickable card that contains an inner button
  • WHEN the user presses Tab to move focus onto the card
  • THEN the focus ring is drawn on the outer container and not on the title
  • AND a subsequent Tab moves focus to the inner button

Referências