# Advanced table patterns — inline edit and tree/hierarchical rows

_Grounded in `packages/web-components/patterns/data-tables-inline-edit-and-tree/` (source:
`data-tables-inline-edit-and-tree.examples.html`; docs route: `/site/patterns/data-tables-inline-
edit-and-tree`) and the underlying `tree.yaml` / `tree-item.yaml` component contracts. This is
the `data-table`-category pattern named "Data Tables — Inline Edit &amp; Tree" in
`pattern-catalog`' index — this file is the deep-dive; `pattern-catalog` is the index entry pointing
here._

Two adjacent but distinct techniques, covered in one pattern because both extend `table-ui`
beyond its own read-only-grid props by composing other primitives around/inside it. They solve
different problems and are not variants of each other.

## When to use which

| Need | Reach for | Not this |
| --- | --- | --- |
| User spends most of their time correcting/refining row data (pricing matrices, inventory counts, bulk metadata) | Inline-edit grid | A detail drawer — that's one-row-at-a-time depth, not bulk correction |
| Rows are recursively nested and the hierarchy IS the data (org charts, file trees, accounting categories) | Tree table (`tree-ui`) | A flat table with a parent-id column — the hierarchy stops reading as structure |

## Inline edit

### Single editing row (the default shape)

The most common form: a normal table where **one row at a time** enters edit mode (double-click
or a pencil button), and only that row's cells swap for `input-ui`/`select-ui` editors. Save /
Cancel actions appear inline in the row's trailing column — never in a modal, which breaks the
table's reading flow.

Built on `table-ui[raw]` wrapping a hand-authored native `<table>` — not `table-ui`'s own
`.columns`/`.data` render path. A real `<table>` gives true column alignment when some rows need
`input-ui` cells and others need plain text, which the data-driven renderer's per-cell type
system doesn't expose a swap-mid-column hook for:

```html
<card-ui>
  <header>
    <span slot="heading" variant="section">Pricing matrix</span>
    <text-ui slot="description" color="subtle">12 rows · 1 unsaved</text-ui>
    <button-ui slot="action" text="Add row" variant="ghost" size="sm" icon-leading="plus"></button-ui>
  </header>
  <section bleed>
    <table-ui raw>
      <table>
        <thead>
          <tr><th>Plan</th><th>Price</th><th>Limits</th><th></th></tr>
        </thead>
        <tbody>
          <tr>
            <td><text-ui strong>Starter</text-ui></td>
            <td><text-ui color="subtle">$0 / month</text-ui></td>
            <td><text-ui color="subtle">5 seats · 1k requests</text-ui></td>
            <td><button-ui icon="pencil-simple" variant="ghost" size="sm" aria-label="Edit"></button-ui></td>
          </tr>
          <tr style="background: var(--a-bg-subtle);">
            <td><text-ui strong>Pro</text-ui></td>
            <td><input-ui name="pro_price" value="$29" size="sm"></input-ui></td>
            <td><input-ui name="pro_features" value="50 seats · 100k requests" size="sm"></input-ui></td>
            <td>
              <row-ui gap="2" align="center">
                <button-ui text="Save" variant="primary" size="sm"></button-ui>
                <button-ui text="Cancel" variant="ghost" size="sm"></button-ui>
              </row-ui>
            </td>
          </tr>
        </tbody>
      </table>
    </table-ui>
  </section>
</card-ui>
```

### Spreadsheet — every cell editable

The full Excel-style shape: every cell renders an `input-ui` (or a typed equivalent — number,
select, date). Tab moves to the next cell; Enter commits and moves down. Saves auto-debounce
(800ms after the last keystroke); a "Save now" button lets a user force a flush before
navigating away. Reach for this only when the user's job genuinely IS to edit most cells — for
occasional corrections, the single-editing-row shape keeps the rest of the table scannable.

Same `table-ui[raw]` + native `<table>` construction, with a per-row Status column (`tag-ui
size="sm" variant="warning"` for "dirty", `variant="muted"` for "clean") surfacing which rows have
unsaved changes.

### Composition rules

- **One editing row at a time is the default.** A single editable row preserves scan-readability
  for the unaffected rows.
- **Highlight the editing row** — a subtle `--a-bg-subtle` background (as an inline `style` on the
  `<tr>` in the example above) keeps the active row obvious after scrolling.
- **Save/Cancel are inline, never modal** — trailing edge of the row.
- **Dirty state surfaces per row, not per cell** — a trailing `tag-ui[variant="warning"]` "dirty"
  tag; per-cell highlighting is too noisy at spreadsheet scale.
- **Auto-save is the right default for spreadsheets** — debounced ~800ms; pair with an explicit
  "Save now" for users who want to force a flush.

## Tree table — hierarchical rows

Use `tree-ui` for the recursion, NOT `table-ui`. Each `tree-item-ui` renders its own chevron +
indent automatically; nested items go inside the parent's default slot:

```html
<card-ui>
  <header>
    <span slot="heading" variant="section">Q4 budget — by department</span>
    <text-ui slot="description" color="subtle">Total: $1.24M · 4 departments · 12 categories</text-ui>
  </header>
  <section bleed>
    <tree-ui style="padding: var(--a-space-2);">
      <tree-item-ui text="Engineering — $620k" icon="cpu">
        <tree-item-ui text="Salaries — $480k"></tree-item-ui>
        <tree-item-ui text="Tooling — $80k"></tree-item-ui>
        <tree-item-ui text="Travel — $60k"></tree-item-ui>
      </tree-item-ui>
      <tree-item-ui text="Operations — $120k" icon="gear"></tree-item-ui>
    </tree-ui>
  </section>
</card-ui>
```

`tree-ui` manages single-selection across the whole subtree and implements WAI-ARIA tree-view
keyboard nav (arrows, Enter/Space, Home/End per `tree-item.yaml`'s host contract). Per ADR-0027
neither `tree-ui` nor `tree-item-ui` auto-imports the other — a consumer page imports both
explicitly. Listen for `tree-select` **on `tree-ui`**, never on individual rows — selection is
managed by the parent and bubbles once (`detail: {item, text, value, ctrlKey, metaKey,
shiftKey}`).

### Composition rules

- **Tree depth caps at 4.** Beyond that, indentation eats the column width and the structure
  stops reading. Reach for a search/filter (`pattern-catalog`' Filter Bar) or breadcrumbs to
  navigate deeper trees instead of nesting further.
- **Every row renders the same shape at every depth** — an icon + text + optional trailing
  `[badge]` metadata. Don't make the root row look structurally different from a leaf; the
  chevron + indent already encode the hierarchy.
- **Aggregates roll up to the parent.** "Engineering — $620k" already sums its children's $480k +
  $80k + $60k — show the total at the parent so users don't have to expand every branch to
  verify a number.
- **Provide a stable `[value]`** on every `tree-item-ui` a consumer will select — `tree-select`'s
  `detail.value` is what downstream code reads to know which node fired, not the display text.
  Use `[open]` to pre-expand branch nodes on initial render; never set `[selected]` declaratively
  on more than one item — `tree-ui` owns selection.

## See also

- `table-ui` — the canonical grid primitive (used for the spreadsheet/inline-edit body) —
  [`base-table.md`](base-table.md).
- `tree-ui` / `tree-item-ui` — the hierarchy primitives, full prop contract in their own yamls.
- `input-ui` / `field-ui` — the cell-editor primitives used inside the native `<table>` body.
- `pattern-catalog`' `data-table` category — Filter Bar (facet chips above a table), Bulk Action
  Toolbar (contextual bar when rows are selected) are the adjacent patterns this one composes
  with, not duplicates of it.
