# 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="outline" 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. Bare text and direct-child elements share the card's normal one-column flow, with space reserved at the logical inline start for the icon:

```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.

Keep alert messages concise. A plain paragraph is sufficient for a very short message. When a title helps people scan the message, use `hgroup` with one context-appropriate heading and one supporting paragraph. Use a card or dialog instead when content needs lists, multiple explanatory paragraphs, or a complex workflow.

An alert may expose one or two actions in a `footer`. Prefer one primary action when possible; when there are two, make the recommended action primary and the other secondary. Actions remain inline when space permits and stack in narrow alert containers.

To make an informational or non-blocking alert dismissible, load htmx in the consuming application and add a native icon button with an `hx-on:click` handler. Alerts about destructive actions or unresolved errors generally should not be dismissible. When dismissal represents a lasting choice, the application should remember it instead of showing the same alert again:

```html
<article data-variant="info" aria-labelledby="registration-title">
  <hgroup>
    <h3 id="registration-title">Registration opens Monday</h3>
    <p>Review your academic plan now so you are ready to add classes.</p>
  </hgroup>
  <footer aria-label="Alert actions">
    <a class="button" href="/academic-plan">Review academic plan</a>
    <a href="/registration">Learn more</a>
  </footer>
  <button
    class="icon flat"
    type="button"
    aria-label="Dismiss alert"
    hx-on:click="this.closest('article')?.remove()"
  >close</button>
</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 preserve the card's normal one-column content flow. Logical inline-start padding reserves space for the absolutely positioned circular status icon, so content stacks and wraps without entering the icon area in either left-to-right or right-to-left layouts. The status icon and optional native dismiss button align to the alert's block start. The optional action footer is the only responsive layout region.

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 logical inline-start padding for the absolutely positioned generated icon while retaining the card's normal one-column content flow.
- CRD-012: Bare text and arbitrary direct-child alert content should stack and wrap within the reserved content area without entering the icon area in left-to-right or right-to-left layouts.
- 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 a native `.icon` button with a descriptive label and an htmx click handler.
- CRD-019: The dismiss button should occupy the alert's block-start, inline-end 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 click attribute 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.
- CRD-023: A concise titled alert should group one context-appropriate heading and one supporting paragraph in `hgroup`, while a very short alert may use a plain paragraph.
- CRD-024: An alert action region should use a direct-child `footer` containing no more than two actions and align those actions to the content start.
- CRD-025: Alert footer actions should remain inline when space permits and stack when the alert container is narrower than `28rem`.

## 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.
- Use a native `<button type="button">` for the dismiss control and give the icon-only button a descriptive `aria-label`; native button semantics provide Enter and Space keyboard activation.

## 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 reserved inline space; 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

- `--button-background`
- `--button-border-color`
- `--button-color-text`
- `--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)
