<!-- Generated by emit-docs.ts — do not edit. -->
# Table

Data table on native table semantics. Holds no state: sorting, selection and pagination are the consumer's (D62, extending D50/D53).

## Props

| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
| `size` | `32 \| 40 \| 48` | 40 | no | Row height in px. |
| `stickyHeader` | `boolean` | — | no | Pins the header while the body scrolls. |
| `sortable` | `boolean` | false | no | Enables the sort affordance on header cells that declare a `sortKey`. |
| `sort` | `TableSortState \| null` | — | no | Controlled sort state; `null` when nothing is sorted. |
| `onSortChange` | `((sort: TableSortState) => void)` | — | no | Called with the **next** sort state, already toggled — store it as given and do not toggle again. A fresh column arrives `"asc"`; an active `"asc"` column emits `"desc"`; an active `"desc"` column emits `"asc"`. Optional in the type because `sortable` may be false; a discriminated union expressing the real contract does not survive docgen's flat prop extraction, which would strip these props from the manifest entirely (D62). |
| `selectable` | `boolean` | false | no | Renders the row-selection checkbox column. |
| `selected` | `ReadonlySet<string>` | new Set<string>() | no | Controlled selection, keyed by each `TableRow`'s `rowId`. |
| `onSelectionChange` | `((selected: ReadonlySet<string>) => void)` | — | no | Called with the next selection. See `onSortChange` on why it is optional. |
| `children` | `ReactNode` | — | yes | TableHead and TableBody. |
| `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |
| `ref` | `Ref<HTMLTableElement>` | — | no | Forwarded ref to the underlying `<table>` element. |

## Theming

Override `--psi-table-*` custom properties at any scope; interactive states derive automatically (L - 0.04 hover, L - 0.08 active).



## Rules

- One accent per visual group; everything else neutral or ghost.
- danger only for actions with real consequences.
- Sizes are px numbers (24|32|40|48), never S/M/L.
- Typography tokens are --psi-text-{size}-{lineHeight}-{weight}.
- Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.
- --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).
- Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.
- Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field).
- Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.
