# Table

## Overview

The table component presents structured, scan-friendly data in rows and columns.

The component should style a native `<table>` with proper table elements such as `<caption>`, `<thead>`, `<tbody>`, `<tr>`, `<th>`, and `<td>`.

## When to use

Use a table when users need to scan or compare structured values across consistent rows and columns.

## When not to use

Do not use a table for page layout, narrative content, inconsistent fields, or content that must reflow freely; use a list, cards, or grouped sections.

## Usage

```html
<table>
  <caption>Team Members</caption>
  <thead>
    <tr>
      <th scope="col" aria-sort="ascending">
        <a href="?sort=name">
          <span>Name</span>
        </a>
      </th>
      <th scope="col">Role</th>
      <th scope="col">Status</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Alex Morgan</td>
      <td>Designer</td>
      <td>Active</td>
    </tr>
  </tbody>
</table>
```

Use this component when the user needs to compare values across multiple rows and columns.

Use a list, cards, or grouped sections instead when the content is primarily narrative, does not share consistent columns, or needs to reflow freely on small screens.

Column labels should stay short and specific so users can scan the header row quickly.

Use native table elements so the content keeps built-in semantics for row and column relationships.

Unless a specific application requirement says otherwise, include a `<caption>` only when another heading or description on the page does not already describe the table. A caption is not required, and omitting redundant captions can preserve limited space on data-dense pages.

Use the default table for data-heavy views that compare many rows, especially when the data has many columns or requires pagination. Wrap it in `.overflow-auto` so its intrinsic column width scrolls inside a bounded region instead of widening the page.

Use `.table-switcher` for a short, typically unpaginated list with a small, known set of fields. Each row should remain understandable as a self-contained record when its cells stack. Repeat each column heading in the corresponding cell's `data-label`; the wrapper uses its own inline size to switch the table into labeled row cards below `40rem`. Because `data-label` renders as plain generated text, it cannot contain a button or link that filters or sorts the table.

Both approaches are responsive. The default table preserves column alignment and scrolls within its bounded region, while the table switcher reformats rows for narrow containers. Keep data-heavy reporting and comparison tables in the default layout; if the results require pagination, the default table is probably the better fit.

## Behavior

Covered by [Playwright behavior tests](../../tests/e2e/data-branding-tokens.spec.js).

- TBL-001: Rows and cells should size from content and the shared table density tokens.
- TBL-002: Comfortable and compact density should come from `--table-row-min-height`, `--table-cell-padding-block`, and `--table-cell-padding-inline`.
- TBL-003: Tables fill their container with `width: 100%` and use the native automatic table layout so intrinsic column content can overflow a bounded wrapper when necessary.
- TBL-004: Header cells should remain visually distinct from body cells through semantic text color and optional inline alignment for labels and sort indicators.
- TBL-005: A header becomes clickable only when its direct child is a link. Plain header text and `th > span` wrappers should remain non-interactive.
- TBL-006: Header links should not use default underline or visited-link styling. They should inherit the table header presentation while keeping native link semantics.
- TBL-007: Functional header icons should appear only inside header links. Do not include sort or action icons in non-interactive headers.
- TBL-008: Icons inside table headers and cells inherit the surrounding table text color unless a nested icon style overrides that behavior.
- TBL-009: Divider treatment should use the shared border and surface tokens rather than table-specific color primitives.
- TBL-010: A table inside a bounded overflow container keeps that region keyboard accessible and programmatically named even when the table has no interactive content.
- TBL-011: A table directly inside `.table-switcher` uses its `max-content` width above `40rem` so wide columns can scroll, then stacks each body row below that container width.
- TBL-012: Stacked cells expose their `data-label` as a visible, non-interactive heading while the native table head remains available to assistive technology.
- TBL-013: A sortable header exposes its state with `aria-sort` on the `<th>` and places its sort link directly inside that header.
- TBL-014: Sortable headers generate an ascending, descending, or unsorted icon from `aria-sort="ascending"`, `aria-sort="descending"`, or `aria-sort="none"`; `aria-sort="other"` uses the unsorted icon for compatibility.
For exact table token values and layout-theme branches, use [table.css](/Users/stuft2/Projects/design-tokens/src/css/table.css) as the source of truth.
## Accessibility

- Use a native `<table>` only for tabular data where each cell relates to a row and column header.
- Include a `<caption>` only when the table is not already described by another heading or description on the page, unless an application requirement specifies otherwise.
- Use `<th scope="col">` for column headers and `<th scope="row">` for row headers when rows need a primary label.
- Keep header labels descriptive enough to make sense when announced with cell values by assistive technology.
- If a header is interactive, use a real `<a>` element so keyboard, focus, and navigation behavior remain intact.
- Put `aria-sort` on the column header, not the link. Use `none` on inactive sortable columns and `ascending` or `descending` only on the currently sorted column. Update the value when sorting changes; the visual icon is generated from that state and remains decorative.
- Give a bounded overflow wrapper `tabindex="0"`, `role="region"`, and an accessible name that references the table caption or the heading or description that identifies the table so keyboard users can enter and scroll it.
- Use `.table-switcher` only for short, simple tables whose rows remain understandable as individual records. Keep paginated, data-heavy, comparison-heavy, sortable, selectable, or multi-level tables in their native scrolling layout.
- Every body cell in a responsive table needs a `data-label` matching its column heading; this visible duplicate supplements rather than replaces native header associations.
- Keep switcher `data-label` values to plain text. They cannot provide buttons or links for filtering or sorting; use the default table when column headings need interactive controls.
- Do not use tables for page layout or purely visual alignment.

## Variables

| Variable | Type | Description |
| --- | --- | --- |
| `--table-actions-cell-width` | `<length>` | Sets the fixed width for action columns; change it when row action controls need more or less reserved space. |
| `--table-background` | `<color>` | Sets the table and body cell background; change it when the table surface should differ from the page or header background. |
| `--table-body-text-color` | `<color>` | Sets body cell text color; change it when table data needs different contrast from surrounding content. |
| `--table-border-style` | `<custom-ident>` | Sets row divider line style; change it only when table separators should use a non-solid style. |
| `--table-border-width` | `<length>` | Sets row divider thickness and participates in density calculations; change it when table separators need a heavier or lighter rule. |
| `--table-cell-padding-block` | `Any` | Sets density-specific vertical cell padding before border compensation; change it to tune comfortable or compact row breathing room. |
| `--table-cell-padding-inline` | `Any` | Sets density-specific horizontal cell padding before compact-column adjustments; change it to tune table scan density. |
| `--table-cell-padding-inline-start` | `Any` | Sets the leading and trailing edge padding for special columns; change it when table edges should align differently from normal cells. |
| `--table-checkbox-cell-width` | `<length>` | Sets the fixed width for checkbox selection columns; change it when selection controls need more or less reserved space. |
| `--table-column-expand-width` | `<length>` | Sets the fixed width for expand columns; change it when disclosure or expansion controls need a wider label or target area. |
| `--table-divider-color` | `<color>` | Sets the base divider token used by table borders; change it to retheme row separation while leaving border style and width intact. |
| `--table-font-weight-body-bold` | `<number>` | Sets responsive cell-label emphasis; change it when stacked table headings need a different weight. |
| `--table-header-background` | `<color>` | Sets header cell background; change it when column headings should sit on a different surface from body cells. |
| `--table-header-column-gap` | `<length>` | Sets spacing between header label content and inline header controls such as sort icons; change it to tighten or loosen heading controls. |
| `--table-header-icon-fill` | `<number>` | Sets the sort indicator fill axis; change it to switch sortable-header icons between outlined and filled glyphs. |
| `--table-header-icon-font-family` | `Any` | Sets the font family for generated sort indicators; change it only when sortable headers use a different icon font. |
| `--table-header-icon-grad` | `<number>` | Sets the sort indicator grade axis; change it to subtly thicken or lighten sortable-header icons. |
| `--table-header-icon-opsz` | `<number>` | Sets the sort indicator optical size axis; change it when sortable-header icons need different optical tuning. |
| `--table-header-icon-size` | `<length>` | Sets the rendered sort indicator size; change it when sortable-header icons should scale independently from other icons. |
| `--table-header-icon-wght` | `<number>` | Sets the sort indicator weight axis; change it when sortable-header icons need heavier or lighter strokes. |
| `--table-header-text-color` | `<color>` | Sets header cell text color; change it when column headings need stronger or subtler emphasis. |
| `--table-row-min-height` | `<length>` | Sets the minimum row height for comfortable and compact density; change it to adjust row target size and vertical rhythm. |

## Depends On

- `--color-accent-subtle`
- `--color-border-subtle`
- `--color-text`
- `--color-text-subtle`
- `--font-weight-body-bold`
- `--icon-fill`
- `--icon-font-family`
- `--icon-grad`
- `--icon-opsz`
- `--icon-size`
- `--icon-wght`
- `--layout-control-border-width`
- `--layout-control-gap`
