# Card

## Overview

The card component presents a self-contained `<article>` or `<figure>` on a contained surface with a subtle border.

The component styles native `<article>` and `<figure>` elements directly so authors can use semantic markup without an extra component class.

For exact card styles, use [card.css](./card.css) as the source of truth.

## When to use

Use a card when a short, self-contained article or figure needs its own surface and boundary, or when a concise alert needs emphasis.

## When not to use

Do not use a card for continuous document flow, repeated structured data, or routine information that does not need alert emphasis.

## Usage

```html
<article aria-labelledby="card-title">
  <header>
    <h3 id="card-title">Quarterly update</h3>
    <p>Shared token adoption is now the default path for new components.</p>
  </header>

  <p>The card groups short related content into a single readable surface without requiring extra presentational wrappers.</p>

  <footer>
    <button class="secondary" type="button">Review</button>
    <button type="button">Publish</button>
  </footer>
</article>
```

When a card introduces a destination rather than an in-page action, put a descriptive native link in the footer and apply `.button` so it receives the action treatment without losing link semantics:

```html
<article aria-labelledby="draft-card-title">
  <header>
    <h3 id="draft-card-title">Draft a new ADR</h3>
  </header>

  <p>Propose an architecture decision for review.</p>

  <footer>
    <a class="button" href="/adrs/new">Draft an ADR</a>
  </footer>
</article>
```

Use the card heading to name the destination or task, the body to describe its purpose, and the footer link to state the navigation outcome. Do not make the whole card clickable or duplicate the destination link in its heading.

```html
<figure aria-labelledby="chart-card-title">
  <img src="adoption-chart.png" alt="Token adoption increased across three releases.">
  <figcaption id="chart-card-title">Token adoption trend</figcaption>
</figure>
```

Images placed directly inside a figure extend through the figure's inline padding and crop with `object-fit: cover`. An image at the start or end of the figure also extends through the corresponding block padding, while captions and other content retain the card spacing.

When a card is refreshing existing content, set `aria-busy="true"` and disable any controls that must not be activated while the update is in progress:

```html
<article aria-busy="true" aria-labelledby="busy-card-title">
  <header>
    <h3 id="busy-card-title">Quarterly update</h3>
  </header>

  <p>Content is being refreshed.</p>

  <footer>
    <button type="button" disabled>Publish</button>
  </footer>
</article>
```

Remove `aria-busy` and restore the controls when the update finishes. Busy-card styling blocks pointer interaction with direct children, while native `disabled` attributes also prevent keyboard activation without removing the article's loading state from the accessibility tree.

### Alert variants

Add `data-variant` to an `<article>` to present a short status message with a generated icon. Text placed directly inside the article is laid out beside the icon and wraps within the content column:

```html
<article data-variant="error">
  Uploaded file could not be processed.
</article>
```

Supported variants are:

- `info` for neutral information
- `success` for a completed or successful action
- `warning` for a condition that may require attention
- `error` for a failed action or problem
- `unavailable` for a feature or resource that cannot currently be used

The base `data-variant` styling defaults to the informational treatment, but authors should use one of the supported values so the message's intent remains explicit.

To make an alert dismissible, load htmx in the consuming application and add an accessible close span with `hx-on:click` and `hx-on:keydown` handlers:

```html
<article data-variant="error">
  Uploaded file could not be processed.
  <span
    class="icon"
    role="button"
    tabindex="0"
    aria-label="Dismiss alert"
    hx-on:click="this.closest('article')?.remove()"
    hx-on:keydown="if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); this.closest('article')?.remove(); }"
  >close</span>
</article>
```

If the consuming application's Content Security Policy applies nonce restrictions that block inline handlers, reconfigure the alert with a CSP-compatible dismissal implementation before using it.

Alert articles use a two-column grid. The first column is reserved for the circular generated icon, while the second uses `minmax(0, 1fr)` so long text wraps beside the icon instead of moving below it. The status icon is created by CSS; the close span is the accessible interactive control and is positioned at the trailing edge of the alert.

Use this component when a short piece of content needs its own surface, separation, and clear boundary from surrounding layout. Avoid using alerts excessively. In many scenarios, other indicators can be used in place of an alert.

Use a plain section, list item, or table row instead when the content is part of a continuous document flow, repeated structured data, or a layout that should stay visually flat.

## Behavior

Covered by [Playwright behavior tests](../../tests/e2e/components.spec.js).

- CRD-001: The card should style native `<article>` and `<figure>` elements directly rather than require a component class.
- CRD-002: Padding, radius, spacing, and foreground/background colors should come from the shared layout and color tokens.
- CRD-003: The shell should expose `--card-border-radius` and `--card-border-width` so its shape and boundary can be customized consistently; nested footer controls derive their inner corner radius from the shell values.
- CRD-004: The component should accept normal flow content such as headings, paragraphs, lists, and button rows.
- CRD-005: The first and last child margins should collapse cleanly against the card padding so authored content does not create uneven outer spacing.
- CRD-006: Optional `header` and `footer` regions should lay out with shared spacing tokens instead of card-specific measurements, with footer content aligned to the end.
- CRD-007: The component should work without JavaScript and without decorative helper elements inside the article.
- CRD-008: Busy cards should retain their layout while direct child content is shown at `48%` opacity with a `4px` blur.
- CRD-009: The generated loading indicator should remain sharp and centered over the dimmed content.
- CRD-010: Busy-card children should ignore pointer interaction while the update is in progress.
- CRD-011: Articles with `data-variant` should reserve a fixed-width first column for the generated icon and a flexible second column for message content.
- CRD-012: Alert text should align to the start of the icon and wrap within its own column.
- CRD-013: Dark-theme alerts should use the same inverse surface treatment as tooltips so they remain distinct from card and page backgrounds.
- CRD-014: Alert icon size, symbol size, background, and foreground should be controlled through the alert custom properties and shared design tokens.
- CRD-015: Images placed directly inside figure cards should cover their available dimensions full-bleed while captions and other figure content retain the card padding.
- CRD-016: Direct card children should be allowed to shrink within the card so intrinsically wide content can manage its own overflow without widening or clipping the page layout.
- CRD-017: Figure cards should allow descendant overlays to extend beyond the card boundary while full-bleed images preserve the card's rounded corners directly.
- CRD-018: A dismissible alert should expose an `.icon` close span with button semantics, a descriptive label, and htmx click and keyboard handlers.
- CRD-019: The close span should occupy the alert's trailing control area, show a pointer cursor, and receive a visible focus ring when focused.
- CRD-020: Alert dismissal should be expressed by the documented htmx attributes rather than generated content or card-specific JavaScript; consuming applications must load htmx.
- CRD-021: Native article and figure cards should retain their surface and subtle border while rendering without a shadow in light and dark themes, without requiring a variant class.
- CRD-022: A card that introduces a navigation choice should use a descriptive heading and summary with a native button-styled link in its footer rather than a linked heading or clickable card surface.

## Accessibility

- Use `<article>` when the content is independently meaningful in the page outline or feed.
- Use `<figure>` when the card contains self-contained media, a diagram, a chart, a table, or code that benefits from a caption.
- Provide a heading when the card content benefits from a clear accessible label.
- Keep interactive controls inside the card keyboard reachable and descriptive on their own.
- For card navigation, keep the heading as text and use a descriptive `<a class="button" href="…">` in the footer so link semantics and open-in-new-tab behavior remain available.
- Use `aria-busy="true"` only while the card is being updated, and remove it when the update finishes.
- Disable native controls individually when they must not be activated during loading; visual dimming and `pointer-events` alone do not prevent keyboard interaction.
- Write alert text that communicates the status without relying on the generated icon or color.
- Static alert content does not need a live-region role. When a message is inserted or updated dynamically and must be announced, use `role="status"` for non-urgent information or `role="alert"` for urgent problems.
  - Do not use `role="alert"` for non-urgent information, as this will interrupt accessibility APIs.
- Do not include an additional decorative icon in alert markup; the visual icon is generated by CSS.
- Give the dismissible close span `role="button"`, `tabindex="0"`, and a descriptive `aria-label`; support both Enter and Space in its `hx-on:keydown` handler.

## Variables

| Variable | Type | Description |
| --- | --- | --- |
| `--alert-background-color` | `<color>` | Sets the circular alert icon background for each alert variant; change it when status indicators need different severity colors. |
| `--alert-border-color` | `<color>` | Sets the border around dark-theme alert cards; change it when the alert surface needs more or less separation in dark contexts. |
| `--alert-icon` | `<string>` | Sets the Material Symbols glyph rendered before alert content; change it to swap the status icon for a variant. |
| `--alert-icon-size` | `<length>` | Sets the size of the circular alert icon container and its grid column; change it when the status marker should occupy more or less space. |
| `--alert-surface-background` | `<color>` | Sets the alert card surface color; change it when alert content needs a different neutral or inverted background. |
| `--alert-surface-color` | `<color>` | Sets the text color used on alert surfaces; change it when the surface background requires a different contrast color. |
| `--alert-symbol-color` | `<color>` | Sets the glyph color inside the circular alert marker; change it when the marker fill changes or warning icons need dark text. |
| `--alert-symbol-size` | `<length>` | Sets the Material Symbols glyph size inside the alert marker; change it when the symbol should be larger or smaller than the container default. |
| `--card-alert-close-focus-ring-size` | `<length>` | Sets the focus outline width and offset for alert close spans; change it to align the close affordance with surrounding controls. |
| `--card-border-color` | `<color>` | Sets the card outline color; change it when cards need clearer separation from the page or dark-theme surfaces. |
| `--card-border-radius` | `<length>` | Sets the outer card corner radius and feeds the inner footer-button radius calculation; change it to adjust card shape globally. |
| `--card-border-width` | `<length>` | Sets the card outline width and reduces the inner radius; change it when card edges need stronger or subtler definition. |
| `--card-color-accent` | `<color>` | Sets --card-color-accent so the component can default to shared theme or layout tokens while exposing an overrideable component API. |
| `--card-color-accent-subtle` | `<color>` | Sets --card-color-accent-subtle so the component can default to shared theme or layout tokens while exposing an overrideable component API. |
| `--card-color-text` | `<color>` | Sets --card-color-text so the component can default to shared theme or layout tokens while exposing an overrideable component API. |
| `--card-control-gap` | `<length>` | Sets --card-control-gap so the component can default to shared theme or layout tokens while exposing an overrideable component API. |
| `--card-control-min-height` | `<length>` | Sets --card-control-min-height so the component can default to shared theme or layout tokens while exposing an overrideable component API. |
| `--card-icon-font-family` | `Any` | Sets --card-icon-font-family so the component can default to shared theme or layout tokens while exposing an overrideable component API. |
| `--card-space-inline` | `<length>` | Sets --card-space-inline so the component can default to shared theme or layout tokens while exposing an overrideable component API. |
| `--card-space-stack` | `<length>` | Sets --card-space-stack so the component can default to shared theme or layout tokens while exposing an overrideable component API. |

## Depends On

- `--color-accent`
- `--color-accent-subtle`
- `--color-border-subtle`
- `--color-border-subtle-inverse`
- `--color-error-strong`
- `--color-info`
- `--color-primary-strong`
- `--color-success-strong`
- `--color-text`
- `--color-text-inverse`
- `--color-unavailable`
- `--color-warning`
- `--icon-font-family`
- `--icon-size`
- `--layout-control-gap`
- `--layout-control-icon-size`
- `--layout-control-min-height`
- `--layout-control-radius`
- `--layout-focus-ring-size`
- `--layout-space-inline`
- `--layout-space-stack`
- `--layout-surface-border-width`
- [Loading](./loading.md)
