# TransactionTable

> The workhorse. Virtualized, sticky, priority-collapsing.

Source: https://lopifi.com/components/transaction-table
Part of: Lopifi — US GAAP books on autopilot, reviewed by a CPA (https://lopifi.com)

---

- **Family:** Ledger & tables — 
- **Status:** Implemented
- **Spec:** https://lopifi.com/docs/catalog/05-ledger-tables

## Install

```bash
npx shadcn@latest add @lopifi/theme
npx shadcn@latest add @lopifi/transaction-table
```

## Specification

> The workhorse. Everything about it is a craft decision.

### Column priority
```
P1  date · description · amount        never dropped
P2  status · account
P3  category · merchant
P4  reference · memo · created-by · tags
```

### Four presentations
| Width | Presentation |
|---|---|
| ≥1280 | Full table, sticky header, sticky total footer |
| 1024–1280 | P4 dropped into a per-row expand |
| 768–1024 | **Sticky description (left) + sticky amount (right), metadata scrolls between** |
| <768 | `TransactionCard` list — a genuine re-rank |

The 768–1024 pattern is the one nobody implements and the one that makes a wide table usable at laptop width: **the label and the number stay pinned; the metadata scrolls between them**, so the two things being correlated are always both on screen. Details — sticky cell backgrounds, `IntersectionObserver`-driven edge fades, inner borders, focus rings that don't clip — in [responsive](../craft/04-responsive-and-input.md#table-collapse).

### Craft
- **Virtualized past 100 rows** (`@tanstack/react-virtual`), with correct `aria-rowcount` / `aria-rowindex` so a screen reader knows the real total.
- **Amounts right-aligned, `tnum`, paren column reserved, hugging the card inset.**
- **Row heights on the grid:** 32 / 40 / 48 by density.
- **Zebra striping: no.** `--border-subtle` row dividers instead. Striping doubles the background changes on a scrolling surface and fights with hover and selection.
- **Selection is a checkbox column**, always visible on touch, hover-or-focus-revealed on desktop. `Shift`-click range select, `Cmd`-click toggle.
- **Sort is instant.** No FLIP, no stagger. High frequency. Every column sorts on its
  value — the amount column on minor units, the status column on workflow urgency rather
  than the alphabet, so one click puts the exceptions on top. Uncontrolled by default;
  pass `sort` + `onSortChange` and the host owns it, which is the only correct
  arrangement once the rows are a page of a larger set.
- **Columns drag.** Widths are px, persisted per user under `columnStorageKey`, with
  `description` taking the slack. See [`ColumnHeader`](#172-columnheader).
- **Sticky header** gains its shadow only once content scrolls beneath (sentinel + `IntersectionObserver`).
- **Sticky total footer** — a single row height plus a 1px top rule. This is the number people are actually looking for.
- **Pending rows** carry a 2px `--warning` left border and their pending status, at full opacity: a row dimmed to 0.6 drops its secondary text under AA (3.2:1). Confirmation removes the border.

### Keyboard
Full grid model — arrows navigate cells, the grid is **one** tab stop. Table in [accessibility](../craft/02-accessibility.md#data-grid-keyboard-model).

### Motion
Tier 0 almost entirely. Row hover is `background-color` at 100ms — no transform, no shadow, no lift. Insert and delete animate; sort, filter, and paginate do not.

## Rules this component obeys

Money is minor units, never floats. `null` is not `0` and neither is redacted. Negatives render in accounting parentheses with the parenthesis column reserved on both sides. Debit and credit are two columns. Per-currency decimal places. Polarity is explicit — up is not always good. Every figure states its period, entity, currency, basis and as-of.
