/* * Table recipe. * * [data-table] → root * [data-table-header] → * [data-table-body] → * [data-table-footer] → * [data-table-column-header] → (body row) * [data-table-cell] → * [data-table-row-detail-trigger] → toggle button (Button consumer) * * Wrapper element is provided by the eidos root: `
` * absorbs `data-size` / `data-variant` / `data-color` / `data-block` / * `data-striped` / `data-hoverable` / `data-sticky-header` so the table * itself can stay clean `
* [data-table-row] →
* [data-table-row-detail] → full-width disclosure
` 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 ``) 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; }
` cells stick to the top of the wrapper. Position is set on * `` (not `