---
name: table-composition
description: >-
  Answers how to display/organize tabular data with table-ui: contained vs
  uncontained chrome, [raw] (consumer-owned body, a separate axis), opt-in
  striped rows, and resize/sort defaults that flip between JS .columns and
  declarative col-def forms, plus inline-edit-grid and tree/hierarchical-row
  patterns. Use for "how do I show a table", "add sorting/filtering", "make
  this table striped", "columns aren't resizable", "card vs bare table",
  "editable/spreadsheet grid", "tree table / nested rows", "huge list of
  rows". NOT for composing the surrounding screen (screen-composition), data
  wiring (data-wiring), OTHER non-table patterns (pattern-catalog), or
  1000+-row virtualized lists (list-window-ui).
disable-model-invocation: false
user-invocable: false
---

# table-composition — display and organize tabular data

`<table-ui>` is a CSS-grid + subgrid data grid (ARIA `role="grid"`, no `<table>` element) with
sorting, selection, filtering, pagination, column resize, keyboard nav, typed cells, and CSV
export built in. Grounded in `packages/web-components/components/table/table.{yaml,class.js,css}`
— trust those over any paraphrase, including this pack's, if they disagree. The advanced patterns
(inline-edit grid, tree table) are grounded in
`packages/web-components/patterns/data-tables-inline-edit-and-tree/`.

## Contained vs uncontained — two DIFFERENT axes, don't conflate them

**Chrome placement (contained vs uncontained) and `[raw]` (own-vs-consumer-owned body) are
unrelated props.** Every real consumer usage confirms this: edge-to-edge tables are always plain
`<table-ui sortable striped …>` — **zero of them set `[raw]`**.

**Contained** — `<table-ui>` stands alone, not inside a card. It paints its own chrome (inset
`box-shadow` border, `--table-bg` background); `--table-radius` defaults to `0` (`table.css:54`,
assuming composition inside `card-ui`), so override it per-instance for its own rounded box.

**Uncontained (edge-to-edge / bleed)** — `<table-ui>` composes inside `<card-ui><section bleed>`
per the yaml's `a2ui.rules` rule 1 (`table.yaml:294-298`) — no `[raw]` involved. `[bleed]` removes
the `<section>`'s padding so the table's own (already-unrounded) border sits flush at the card's
inner edge instead of floating in a padded gap; table-ui keeps painting its own chrome either way.

**`[raw]` is a separate axis entirely — own data lifecycle vs consumer-owned body — with exactly
one real use.** `render()` short-circuits completely when `[raw]` is set (`table.class.js:622`,
`if (this.raw) return;`) — no header injection, no `.data`/`.columns` reconciliation, no empty/
loading overlays, even when `.columns`/`.data` ARE set (`table.test.js:323-330`). There is no
edge-to-edge-but-still-data-driven combination — `[raw]` always means the consumer supplies the
entire body. Both code shapes: [`references/base-table.md`](references/base-table.md).

Reach for `[raw]` only for the consumer-owned-body case (spreadsheet/inline-edit —
[`advanced-patterns.md`](references/advanced-patterns.md)); never on a table you still want
table-ui to render from `.columns`/`.data`.

## Defaults that surprise — read before assuming

| Prop | Default | Convention |
| --- | --- | --- |
| `striped` | `false` | **Opt-in** — not the table's baseline appearance despite how common it looks in demos. |
| Per-column `resizable`/`sortable` — **JS `.columns` form** | on unless set `false` | **Opt-out** per column. |
| Per-column `resizable`/`sortable` — **declarative `<col-def>` form** | off unless attribute present | **Opt-in — the inverse of the JS form.** Pick the form deliberately: `<col-def>` needs the attribute stated on every resizable/sortable column, `.columns` needs it stated only to turn one OFF. |
| `sortable` (host) | `false` | Host-level gate — required in addition to whichever per-column default above applies. |
| `wrap` (host) / `data-wrap` (per-cell) | `false` | Cells truncate single-line by default; `[wrap]` opts the whole table into multi-line, `[data-wrap]` opts in one cell/column surgically. |
| `selectable`, `expandable`, `loading`, `paginate` | all `false`/`0` | All opt-in; `paginate="0"` means "render every row, no pager." |

Full rationale + citations for every row: [`base-table.md`](references/base-table.md) §Defaults,
which also has the full column/header/event contract (sort, filter, pin, cell types, CSV export,
table-toolbar-ui pairing).

## Advanced: inline edit and tree tables

Two distinct compound patterns, not variants of `<table-ui>`'s own props — both compose existing
primitives rather than adding table-ui edit/tree modes:

- **Inline-edit grid** — one row (or, for full spreadsheets, every cell) swaps its static cells
  for `<input-ui>`/`<select-ui>` editors. Built on `<table-ui raw>` wrapping a native `<table>`,
  because per-cell custom editors need real `<table>` column alignment the data-driven render
  path doesn't give a consumer.
- **Tree table** — hierarchical/recursive rows. Built on `<tree-ui>` + `<tree-item-ui>`, NOT
  `<table-ui>` — a flat table with a parent-id column is a worse shape for genuinely nested data.

Full anatomy, composition rules, and the when-to-use split (inline-edit vs a detail drawer; tree
table vs a flat parent-id table): [`advanced-patterns.md`](references/advanced-patterns.md).

## Consult table

| Ask | Answer from |
| --- | --- |
| "contained or uncontained/bleed table" | this file, above — don't reach for `[raw]`, it's a different axis |
| "is striped/resizable/sortable on by default" | this file's defaults table — resizable/sortable flips between the JS `.columns` and declarative `<col-def>` forms |
| "table needs to hold 1000+ / huge rows" | `[paginate]` for hundreds of rows; `list-window-ui` (virtualized) for 1,000+ — table-ui has no virtual-scroll mode |
| "sort / filter / pin / resize a column", "what cell types exist", "toolbar chrome above a table" | [`base-table.md`](references/base-table.md) |
| "every row should cover default/loading/empty/error" | `data-wiring`'s four-state rule for error (table-ui natively covers default/loading/empty) |
| "editable table / spreadsheet / bulk row correction" | [`advanced-patterns.md`](references/advanced-patterns.md) §Inline edit |
| "nested rows / org chart / file tree / expand-collapse hierarchy" | [`advanced-patterns.md`](references/advanced-patterns.md) §Tree table |
| "other data-table-shaped patterns" (Filter Bar, Bulk Action Toolbar, …) | `pattern-catalog`'s `data-table` category |
| "wire the table's data/hydration" | `data-wiring` — property-API population |
| "build the whole screen around this table" | `screen-composition` |

Surrounding-screen composition is `screen-composition`'s job; data/state ownership is
`data-wiring`'s.
