A table whose columns are data. It keeps scope on the headers, one source of truth for colSpan, and never hides row actions behind hover.
filter; as JSX it would be a condition in the header and in every row.<table> gives it meaning it does not have.columns: readonly IngotColumn<Row>[] — Columns as data — adding one cannot break colSpan. Column properties are in the table below.rows: readonly Row[] — The page of data currently being rendered.rowKey: (row: Row) => string — Stable row identity.empty: ReactNode — What to show instead of rows — typically IngotEmptyState.loading: boolean — The table gets aria-busy and a single role=status row.loadingLabel: string — Translated "Loading…". Required once loading can happen.actions: (row: Row) => ReactNode — Row actions; adds a trailing column.actionsLabel: string — Header of the actions column — for screen readers only.caption: string — Table description for screen readers, rendered off-screen.stickyHeader: boolean — Keeps the header visible inside a height-constrained scroll box.density: "default" | "compact" — compact tightens cell padding to 8px — for screens where rows per screen matter.sort: IngotSort — The current sort {key, dir}. The table never sorts — the order is whatever rows holds.onSortChange: (sort: IngotSort) => void — Click on a sortable header: inactive → asc, asc ↔ desc.selectedKeys: ReadonlySet<string> — Keys of the selected rows (rowKey). Together with onSelectedKeysChange it enables the checkbox column.onSelectedKeysChange: (keys: ReadonlySet<string>) => void — The new set after every selection change — a row as well as select/clear all.selectAllLabel: string — Translated label of the "select all" checkbox in the header.selectRowLabel: (row: Row) => string — Translated label of a row checkbox ("Select {name}").bulkbar: ReactNode — The bulk-action bar above the table; shown only while the selection is non-empty.toolbar: ReactNode — Filters above the table, inside its frame. Turns the frame on.footer: ReactNode — Start of the footer — typically the count (“36 of 36 articles”). Turns the frame on.footerEnd: ReactNode — End of the footer — typically IngotPagination. Turns the frame on.groupBy: (row: Row) => string — A row's group key. Consecutive rows with the same key share one section row; the order of rows decides, the table does not regroup.groupHeader: (key: string, rows: readonly Row[]) => ReactNode — Content of the section row — the caller composes the translated name and count. Without it nothing is grouped.layout: IngotTableLayout — How the user arranged the columns: order, hidden columns, widths. Read defensively — a layout stored for an older set of columns cannot break the table.onLayoutChange: (layout: IngotTableLayout) => void — Turns on arranging: the column picker at the end of the header, a menu on every column, resizing and dragging headers. Storing the layout is the caller's.onLayoutReset: () => void — Shows “Reset to default” in the column picker — the caller drops the stored layout.keyboard: boolean — Walking the rows with the keyboard while the scroll box has focus: J/K move, X select, Enter open. With a footer it prints the shortcuts. Does nothing with stickyHeader.onRowOpen: (row: Row) => void — Enter on the active row.rowClassName: (row: Row) => string | undefined — Whole-row state (not selectable, highlighted), not per-cell styling.rowDetail: (row: Row) => ReactNode — A row's detail in a full-width row directly under it; null = closed. The caller owns what is open — the toggle with aria-expanded belongs in a cell.className: string — Pass-through class on the table, typically min-w-[40rem].rowTestId: (row: Row) => string — data-testid of a row — some screens' end-to-end tests hang off it.testId: string — data-testid of the table.