Skip to content

role container

Card draft v0.1.0

A surface that groups a title and related content as one unit.

A card gathers what belongs together, a title and its content, on a raised surface with its own edge, so a page of many things reads as a grid of units: a project in a list of projects, a plan in a pricing page, a setting group. It is a surface, not a control: a card that opens something holds a link or a button; a card is never itself clickable. Its surface level follows the Color foundation.

Web implementation

Root .ds-card on a <div>. Properties map to attributes: surface → data-surface

packages/components/src/components/card/card.css
/*
 * Card — vanilla CSS reference implementation.
 * Spec:   packages/components/specs/components/card.spec.json
 * Tokens: packages/tokens/dist/css/components/card.css (generated from packages/tokens/src/components/card.tokens.json)
 *
 * Rules:
 *  - Only --card-* custom properties may appear here. No raw values, no semantic tokens.
 *  - The root is a plain container on a surface level of the Color foundation; the surface is a
 *    data attribute; a card has no interaction state of its own.
 */

.ds-card {
  --_background: var(--card-container-background-level-1);
  --_border-color: var(--card-container-border-color-level-1);

  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: var(--card-container-gap);
  min-inline-size: 0;
  padding: var(--card-container-padding);
  border: var(--card-container-border-width) solid var(--_border-color);
  border-radius: var(--card-container-border-radius);
  background-color: var(--_background);
  color: var(--card-description-color);
}

.ds-card[data-surface="level-2"] {
  --_background: var(--card-container-background-level-2);
  --_border-color: var(--card-container-border-color-level-2);
}

.ds-card__title {
  margin: 0;
  color: var(--card-title-color);
  font-family: var(--card-title-typography-font-family);
  font-size: var(--card-title-typography-font-size);
  font-weight: var(--card-title-typography-font-weight);
  line-height: var(--card-title-typography-line-height);
  letter-spacing: var(--card-title-typography-letter-spacing);
}

.ds-card__description {
  margin: 0;
  color: var(--card-description-color);
  font-family: var(--card-description-typography-font-family);
  font-size: var(--card-description-typography-font-size);
  font-weight: var(--card-description-typography-font-weight);
  line-height: var(--card-description-typography-line-height);
  letter-spacing: var(--card-description-typography-letter-spacing);
}
packages/components/src/components/card/card.template.mjs
/**
 * Card — HTML template for the web implementation.
 * Pure function of the spec properties (packages/components/specs/components/card.spec.json) -> HTML string.
 * A plain container (a div: no role of its own) on a surface level, with a heading as its title
 * and a paragraph as its description; `headingLevel` (2 to 6, default 3) fits the card into the
 * page's heading outline.
 */
import { escapeHtml, escapeAttr } from '../../lib/template-utils.mjs';

export const meta = { id: 'card', selector: '.ds-card' };

export function render(props = {}) {
  const { title = 'Title', description = null, surface = 'level-1', headingLevel = 3, state = null } = props;
  const level = Math.min(6, Math.max(2, Number(headingLevel) || 3));
  const attrs = ['class="ds-card"', `data-surface="${escapeAttr(surface)}"`];
  if (state && state !== 'default') attrs.push(`data-state="${escapeAttr(state)}"`);
  const parts = [`<h${level} class="ds-card__title" data-part="title">${escapeHtml(title)}</h${level}>`];
  if (description) parts.push(`<p class="ds-card__description" data-part="description">${escapeHtml(description)}</p>`);
  return `<div ${attrs.join(' ')}>${parts.join('')}</div>`;
}

No other platform implementation recorded yet. Platform token outputs live under Platforms.