`
* absorbs `data-size` / `data-variant` / `data-color` / `data-block` /
* `data-striped` / `data-hoverable` / `data-sticky-header` so the table
* itself can stay clean `
` semantics.
*
* Sizes — xs..xl on the wrapper; cascades to row block-size, cell
* padding and font-size.
*
* Variant `'surface' | 'outline' | 'ghost'` (ControlVariant).
* - surface: card chrome around the whole table
* - outline: border only, transparent background
* - ghost: no chrome
*
* Color cascades the accent for selected rows + sort indicator + focus.
*/
[data-table-root] {
--_table-radius: var(--radius-md);
--_table-row-block-size: var(--control-height-md);
--_table-cell-px: var(--space-3);
--_table-cell-py: var(--space-2);
--_table-font-size: var(--font-size-sm);
--_table-header-bg: var(--color-surface-subtle);
--_table-row-hover-bg: var(--color-surface-overlay);
--_table-row-striped-bg: var(--color-surface-subtle);
--_table-divider: var(--color-border-default);
--_table-accent: var(--color-primary-solid);
--_table-accent-soft: var(--color-primary-element);
position: relative;
display: block;
font-family: var(--font-ui);
color: var(--color-content-primary);
border-radius: var(--_table-radius);
overflow: hidden;
}
[data-table-root][data-block] {
inline-size: 100%;
}
/* ── Variant — surface (default) ─ */
[data-table-root][data-variant='surface'] {
border: 1px solid var(--_table-divider);
background: var(--color-surface-default);
}
[data-table-root][data-variant='outline'] {
border: 1px solid var(--_table-divider);
background: transparent;
}
[data-table-root][data-variant='ghost'] {
border: 1px solid transparent;
background: transparent;
}
/* ── Sizes ─ */
[data-table-root][data-size='xs'] {
--_table-row-block-size: var(--control-height-xs);
--_table-cell-px: var(--space-2);
--_table-cell-py: var(--space-1);
--_table-font-size: var(--font-size-xs);
}
[data-table-root][data-size='sm'] {
--_table-row-block-size: var(--control-height-sm);
--_table-cell-px: var(--space-2);
--_table-cell-py: var(--space-1);
--_table-font-size: var(--font-size-sm);
}
[data-table-root][data-size='lg'] {
--_table-row-block-size: var(--control-height-lg);
--_table-cell-px: var(--space-4);
--_table-cell-py: var(--space-3);
--_table-font-size: var(--font-size-md);
}
[data-table-root][data-size='xl'] {
--_table-row-block-size: var(--control-height-xl);
--_table-cell-px: var(--space-5);
--_table-cell-py: var(--space-3);
--_table-font-size: var(--font-size-lg);
}
/* ── Color cascades ─ */
[data-table-root][data-color='secondary'] {
--_table-accent: var(--color-secondary-solid);
--_table-accent-soft: var(--color-secondary-element);
}
[data-table-root][data-color='affirm'] {
--_table-accent: var(--color-affirm-solid);
--_table-accent-soft: var(--color-affirm-element);
}
[data-table-root][data-color='risk'] {
--_table-accent: var(--color-risk-solid);
--_table-accent-soft: var(--color-risk-element);
}
[data-table-root][data-color='threat'] {
--_table-accent: var(--color-threat-solid);
--_table-accent-soft: var(--color-threat-element);
}
[data-table-root][data-color='fulfill'] {
--_table-accent: var(--color-fulfill-solid);
--_table-accent-soft: var(--color-fulfill-element);
}
[data-table-root][data-color='loss'] {
--_table-accent: var(--color-loss-solid);
--_table-accent-soft: var(--color-loss-element);
}
[data-table-root][data-color='neutral'] {
--_table-accent: var(--color-neutral-solid);
--_table-accent-soft: var(--color-neutral-element);
}
/*
* Scrolling region — only when consumer sets `maxHeight`. Pure CSS
* overflow auto with overscroll containment so wheel scrolling inside
* the table doesn't bubble to the page.
*/
[data-table-root][data-scrollable] {
overflow: auto;
overscroll-behavior: contain;
}
/*
* Stable rows — reserve a vertical floor of `stableRows` row-heights
* (plus the header). Keeps the wrapper height steady so the surrounding
* layout (pagination, sticky footers, sibling cards) doesn't jump as
* filters / pageSize change the visible row count. Ignored when the
* wrapper is already capped by `maxHeight` (scroll region).
*
* The consumer sets `--_table-stable-rows` via inline style; the recipe
* multiplies by `--_table-row-block-size` (already scaled by `data-size`)
* and adds one row for the header.
*/
[data-table-root][data-stable-rows] {
/*
* Real row block-size at render = `--_table-row-block-size` (control
* height, the centred content) + 2 × `--_table-cell-py` (cell vertical
* padding) + 1px (cell `border-block-end`). Sum × (rows + 1 header).
*/
min-block-size: calc(
(var(--_table-row-block-size) + var(--_table-cell-py) * 2 + 1px)
* (var(--_table-stable-rows, 0) + 1)
);
}
[data-table-root][data-disabled] {
opacity: 0.55;
pointer-events: none;
}
/* ── Table element ─ */
[data-table] {
inline-size: 100%;
border-collapse: separate;
border-spacing: 0;
font-size: var(--_table-font-size);
line-height: var(--leading-ui);
color: inherit;
caption-side: top;
}
/* ── Header (thead) ─ */
[data-table-header] {
background: var(--_table-header-bg);
}
/*
* Sticky header — when the wrapper is scrollable AND `stickyHeader` is on,
* the `| ` cells stick to the top of the wrapper. Position is set on
* ` | ` (not ``) because `` is not a valid containing
* block for `position: sticky` in legacy rendering paths.
*/
[data-table-root][data-sticky-header] [data-table-column-header] {
position: sticky;
inset-block-start: 0;
z-index: 2;
background: var(--_table-header-bg);
}
/* ── ColumnHeader (th) ─ */
[data-table-column-header] {
box-sizing: border-box;
padding-block: var(--_table-cell-py);
padding-inline: var(--_table-cell-px);
text-align: start;
font-weight: var(--font-weight-semibold, 600);
color: var(--color-content-primary);
border-block-end: 1px solid var(--_table-divider);
white-space: nowrap;
vertical-align: middle;
user-select: none;
}
[data-table-column-header][data-sortable] {
cursor: pointer;
}
[data-table-column-header][data-sorted] {
color: var(--_table-accent);
}
[data-table-column-header][data-pinned] {
background: var(--_table-header-bg);
}
/* ── Body (tbody) ─ */
[data-table-body] [data-table-row] {
background: var(--color-surface-default);
transition: background var(--duration-fast) var(--ease-default);
}
[data-table-root][data-variant='ghost'] [data-table-body] [data-table-row] {
background: transparent;
}
/* Striped — alternate body rows. Skip when ghost (no chrome). */
[data-table-root][data-striped]:not([data-variant='ghost'])
[data-table-body]
[data-table-row]:nth-of-type(even) {
background: var(--_table-row-striped-bg);
}
/* Hoverable — row hover highlight. Drop when row is selected. */
[data-table-root][data-hoverable]
[data-table-body]
[data-table-row]:not([data-selected]):hover {
background: var(--_table-row-hover-bg);
}
/* Selected row — accent tint. Overrides hover + striped. */
[data-table-body] [data-table-row][data-selected] {
background: var(--_table-accent-soft);
color: var(--color-content-primary);
}
/* Last row has no bottom divider — the wrapper border handles the edge. */
[data-table-body] [data-table-row]:last-of-type [data-table-cell] {
border-block-end: 0;
}
/* ── Row (tr) ─ */
[data-table-row] {
outline: none;
}
[data-table-row]:focus-visible {
outline: var(--focus-ring-width) solid var(--focus-ring-color);
outline-offset: -1px;
}
/* ── Cell (td) ─ */
[data-table-cell] {
box-sizing: border-box;
padding-block: var(--_table-cell-py);
padding-inline: var(--_table-cell-px);
border-block-end: 1px solid var(--_table-divider);
vertical-align: middle;
color: inherit;
}
/*
* Pinned cells take the row's background so the sticky offset doesn't
* reveal content behind them. Cascade from `data-pinned` on either the
* cell or the header.
*/
[data-table-cell][data-pinned] {
background: inherit;
}
/* ── Footer (tfoot) ─ */
[data-table-footer] {
background: var(--_table-header-bg);
}
[data-table-footer] [data-table-cell] {
font-weight: var(--font-weight-semibold, 600);
border-block-end: 0;
border-block-start: 1px solid var(--_table-divider);
}
/* ── RowDetail (disclosure) ─ */
[data-table-row-detail] {
background: var(--color-surface-subtle);
}
[data-table-row-detail][data-state='closed'] {
display: none;
}
[data-table-row-detail] > td {
padding: var(--space-3) var(--_table-cell-px);
border-block-end: 1px solid var(--_table-divider);
color: var(--color-content-secondary);
}
/* ── SortButton (eidos helper) ─ */
[data-table-sort-trigger] {
display: inline-flex;
align-items: center;
gap: var(--space-1);
padding: 0;
margin: 0;
border: 0;
background: transparent;
color: inherit;
cursor: pointer;
font: inherit;
user-select: none;
border-radius: var(--radius-sm);
}
[data-table-sort-trigger]:focus-visible {
outline: var(--focus-ring-width) solid var(--focus-ring-color);
outline-offset: 2px;
}
[data-table-sort-trigger][data-sorted] {
color: var(--_table-accent);
}
[data-table-sort-indicator] {
display: inline-flex;
align-items: center;
justify-content: center;
inline-size: 1em;
block-size: 1em;
opacity: 0.55;
transition: opacity var(--duration-fast) var(--ease-default);
}
[data-table-sort-trigger][data-sorted] [data-table-sort-indicator] {
opacity: 1;
}
|