# Card

A block container with a clean surface, hairline border, and an interactive state when the card is rendered as a link or button. Class + data-attribute API: one `.x-card` class plus `data-variant` and `data-size`.



## Install

```ts
import "elements-kit/ui/styles.css";

import "elements-kit/ui/styles/palette/gray.css";
import "elements-kit/ui/styles/neutral/gray.css";

import "elements-kit/ui/card/card.css";
```

`unset.css` is **not required** for `<div>`s. When the card is a `<button>` or `<a>`, add `unset` first to neutralise the native rendering before applying `.x-card`.

## API

```html
<div class="x-card" data-variant="surface" data-size="3">
  Card content.
</div>
```

| Attribute                                  | Values                                            |
| ------------------------------------------ | ------------------------------------------------- |
| `data-variant`                             | `surface` (default), `elevated`, `borderless`     |
| `data-size`                                | `1`, `2`, `3` (default), `4`, `5`                 |
| `data-radius` *(on a parent)*              | `none`, `small`, `medium`, `large`, `pill`        |
| `data-material-background` *(on a parent)* | `solid`, `translucent`                            |
| `data-accent` *(on a parent)*              | any imported color scale — used by the focus ring |

## Sizing

`data-size` scales padding and border-radius together.

| `data-size`     | padding     | radius       |
| --------------- | ----------- | ------------ |
| `1`             | `--space-3` | `--radius-4` |
| `2`             | `--space-4` | `--radius-4` |
| `3` *(default)* | `--space-5` | `--radius-5` |
| `4`             | `--space-6` | `--radius-5` |
| `5`             | `--space-8` | `--radius-6` |

Set `data-radius` on an ancestor to scale corner curvature globally; `--scaling` on `<html>` scales padding and radius together.

## Variants

* **`surface`** — 1px hairline border (`--neutral-a5`). Default. The everyday card.
* **`elevated`** — same border plus a tailored drop shadow. Uses dual outer-on-element + inner-on-`::after` shadows (`--card-elevated-box-shadow-outer` / `-inner`) with matching 6-layer counts across neutral/hover/active so the lift transitions smoothly. Use for primary, hover-able cards.
* **`borderless`** — no border, no background. Hover fills with `--neutral-a3`. Use inside dense lists or as clickable rows.

## Inset

`data-inset` on a direct child of `.x-card` makes that child bleed past the card's padding to the card's edge. Common case: a hero image at the top of a card.

```
without inset:                with data-inset="top":

┌─────────────────────┐       ┌─────────────────────┐
│  ░░░░░░░░░░░░░░░░░  │       │░░░░░░░░░░░░░░░░░░░░░│
│  ░░░ HERO IMAGE ░░  │       │░░░░ HERO IMAGE ░░░░░│
│  ░░░░░░░░░░░░░░░░░  │       │░░░░░░░░░░░░░░░░░░░░░│
│                     │       ├─────────────────────┤
│  Heading            │       │  Heading            │
│  Body               │       │  Body               │
└─────────────────────┘       └─────────────────────┘
```

```html
<div class="x-card">
  <img data-inset="top" src="/hero.webp" alt="" />
  <h3>Title</h3>
  <p>Body</p>
</div>
```

| Value                 | Side                                | Use case                                                                                                                 |
| --------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `data-inset="top"`    | block-start                         | Vertical card — hero at top                                                                                              |
| `data-inset="bottom"` | block-end                           | Vertical card — footer media at bottom                                                                                   |
| `data-inset="start"`  | inline-start (left LTR / right RTL) | Horizontal card — leading media                                                                                          |
| `data-inset="end"`    | inline-end                          | Horizontal card — trailing media                                                                                         |
| `data-inset="fill"`   | every side                          | A scroller filling the card, e.g. a [long dialog](/ui/toolbar#a-long-dialog). Pad its content with `var(--card-padding)` |

**Pick the pair that matches your card's orientation.** `top` / `bottom` are physical (block axis). `start` / `end` are logical (inline axis) and flip automatically in RTL.

The inset element rounds the corners it bleeds into and squares the others. The corner radius is computed from the card's `--card-border-radius`, so it always lines up flush with the card's border.

> **Don't**
>
> Wrap inset elements in extra parents — `data-inset` must be a **direct child** of `.x-card`. The selector reads padding vars from the immediate parent card.

> **Do**
>
> For full-bleed media (image fills the whole card), set `data-padding="none"` on the card or stack two insets (top + bottom). The kit covers the common single-side cases.

## Surface material

The card reads `var(--color-material, var(--color-surface))` for its background, so by default it sits on the theme surface color. Wrap any ancestor with `data-material-background="translucent"` to swap to a frosted backdrop:

```html
<section data-material-background="translucent">
  <div class="x-card">Frosted</div>
</section>
```

Available values: `solid`, `translucent`. `translucent` adds a 64px backdrop blur.

## Interactive cards

When the card itself is an anchor, button, or label, hover and active states apply. Wrap the underlying element with `unset` to drop native styling first:

```html
<a href="/post" class="unset x-card" data-variant="elevated">
  <h3>Post title</h3>
  <p>Body…</p>
</a>
```

| State  | `surface`                      | `elevated`                     | `borderless`              |
| ------ | ------------------------------ | ------------------------------ | ------------------------- |
| hover  | border bumps to `--neutral-a7` | shadow lifts to `--shadow-3`   | fills with `--neutral-a3` |
| active | border at `--neutral-a6`       | shadow returns to `--shadow-2` | fills with `--neutral-a4` |

Non-interactive cards (a plain `<div>`) skip these states entirely — no need to gate them with media queries.

## Focus

When a card is focusable, focus shows a 2px outline using `--focus-8`. Set `data-accent` on an ancestor to pick the accent color for the focus ring.

```html
<div data-accent="mint">
  <a href="/post" class="unset x-card">…</a>
</div>
```

## Theming

Light/dark flips automatically via the `.dark` class on a parent — see [Light & dark](/ui/styles#light--dark). The surface picks up `--color-material` (translucent backdrop) or `--color-surface` (solid neutral), both defined in [theme.css](/ui/styles).

## Accessibility

`.x-card` ships with no ARIA role — it's a pure visual class. When the card is interactive, render it as an `<a>` or `<button>` so it picks up the correct semantics, keyboard handling, and the kit's focus styles. Wrap nested links in a non-interactive card with care: nested interactives are an accessibility footgun.
