Data Table
A flexible, Compose Multiplatform data table built entirely on Foundation APIs.
Features:
Column sorting (single and multi-column with Ctrl+click)
Row selection (none, single, multi)
Row expansion with custom content
Row hover highlight and alternating row colors
Frozen/pinned columns
Column resizing via drag handles
Grouping with custom group header and summary rows
Pagination with configurable items-per-page
Column visibility toggle
Text overflow / ellipsis per column
Custom sort comparators
Right-click context menu callback
Keyboard navigation (arrow keys, Enter, Space, Home, End)
Programmatic scroll-to-row via DataTableState
Customizable colors and text styles without any theming framework
Selection and expansion are tracked by row key rather than by item. Callers hoist a Set<Any> of keys produced by itemKey, so both survive item instances being replaced (a refresh from a repository, for example) and neither requires the item type to implement equals/hashCode.
Parameters
The item type for each row.
Produces a stable, unique key per row — a database id or similar. Used for LazyColumn item identity and as the identity of a row in selectedKeys and expandedKeys. Keys must be unique across items: two rows sharing a key select, expand, and recycle as one row.
Keys of the currently selected rows.
Invoked with the new set of selected keys.
Keys of the currently expanded rows.
Invoked with the new set of expanded keys.
Active single-column sort. Read only when onSortChange is supplied.
Invoked when a header is clicked. Supplying it makes sorting controlled: the table renders sortBy and never changes it, so the caller must feed the new value back. Leave it null to let the table own sort state.
Active multi-column sort. Controlled by onMultiSortChange the same way.
When true the table does not reorder items — the caller has already sorted them, typically in a database query. Headers still show sort indicators and report clicks.
Active zero-based page. Read only when onPageChange is supplied, which makes pagination controlled in the same way as sorting.
When true items is already the current page and the table does not slice it. Requires totalItems.
Row count across every page. Defaults to items.size, which is correct unless manualPagination is on — then only the caller knows the true total.