DataTable

@Composable
fun <T> DataTable(items: List<T>, headers: List<DataTableHeader<T>>, itemKey: (T) -> Any, modifier: Modifier = Modifier, state: DataTableState = rememberDataTableState(), showSelect: Boolean = false, selectionMode: SelectionMode = if (showSelect) SelectionMode.MULTI else SelectionMode.NONE, selectedKeys: Set<Any> = emptySet(), onSelectionChange: (Set<Any>) -> Unit? = null, showExpand: Boolean = false, expandedKeys: Set<Any> = emptySet(), onExpandChange: (Set<Any>) -> Unit? = null, expandContent: @Composable (T) -> Unit? = null, density: DataTableDensity = DataTableDensity.DEFAULT, colors: DataTableColors = DataTableDefaults.colors(), textStyles: DataTableTextStyles = DataTableDefaults.textStyles(), sortBy: SortState = SortState(), onSortChange: (SortState) -> Unit? = null, multiSortBy: List<SortState> = emptyList(), onMultiSortChange: (List<SortState>) -> Unit? = null, manualSorting: Boolean = false, resizableColumns: Boolean = false, minColumnWidth: Dp = 40.dp, hideDefaultHeader: Boolean = false, hideDefaultFooter: Boolean = false, loading: Boolean = false, loadingContent: @Composable () -> Unit? = null, headerContent: @Composable () -> Unit? = null, footerContent: @Composable () -> Unit? = null, noDataContent: @Composable () -> Unit? = null, groupBy: (T) -> String? = null, groupHeaderContent: @Composable (String, List<T>) -> Unit? = null, groupSummaryContent: @Composable (String, List<T>) -> Unit? = null, onRowClick: (T) -> Unit? = null, onRowDoubleClick: (T) -> Unit? = null, onRowContextMenu: (T, Offset) -> Unit? = null, showPagination: Boolean = false, itemsPerPage: Int = 10, currentPage: Int = 0, onPageChange: (Int) -> Unit? = null, itemsPerPageOptions: List<Int> = listOf(10, 25, 50, 100), onItemsPerPageChange: (Int) -> Unit? = null, manualPagination: Boolean = false, totalItems: Int? = null, showScrollbars: Boolean = true, scrollbarThickness: Dp = 8.dp)(source)

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

T

The item type for each row.

itemKey

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.

selectedKeys

Keys of the currently selected rows.

onSelectionChange

Invoked with the new set of selected keys.

expandedKeys

Keys of the currently expanded rows.

onExpandChange

Invoked with the new set of expanded keys.

sortBy

Active single-column sort. Read only when onSortChange is supplied.

onSortChange

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.

multiSortBy

Active multi-column sort. Controlled by onMultiSortChange the same way.

manualSorting

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.

currentPage

Active zero-based page. Read only when onPageChange is supplied, which makes pagination controlled in the same way as sorting.

manualPagination

When true items is already the current page and the table does not slice it. Requires totalItems.

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.