Data Table

Low-level table primitives plus a typed DataTable with sorting, row selection, and pagination.

Overview

This page documents two layers that ship from the same Table.kt source:

  • Table primitivesTable, TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell, TableCaption. Use these when you want full control over layout, custom headers, or non-rectangular data.
  • DataTable<T> — a typed, batteries-included data table. It owns state for sorting, optional checkbox selection, and Previous/Next pagination, and lets you swap in a custom pagination UI through a slot.

Both layers share the same horizontal-scroll model: the table is not shrunk to viewport on mobile. Declare each column's width as a fixed Dp and the wrapper will scroll horizontally when needed. Header and body cells in the same column must share the same Modifier.width(...) so the columns line up.

Anatomy

Table {
    TableHeader {
        TableRow {
            TableHead { /* header cell */ }
            // ...one TableHead per column
        }
    }
    TableBody {
        TableRow {
            TableCell { /* body cell */ }
            // ...one TableCell per column
        }
        // The final body row should pass showBottomBorder = false
    }
    TableFooter { // optional
        TableRow(showBottomBorder = false) {
            TableCell { /* footer cell */ }
        }
    }
}
TableCaption(text = "Optional caption") // rendered outside the Table container

Table primitives

A hand-rolled invoice table. Each column declares a fixed Dp width and both TableHead and TableCell cells reuse the same width so the columns stay aligned under the horizontal scroller. The last body row passes showBottomBorder = false to drop its bottom border.

TableFooter paints a muted background and a top border — typically used for totals. The footer row's showBottomBorder = false keeps the rounded corner clean. TableCaption is rendered outside the Table container so it sits below the scrollable area.

Data Table

DataTable<T> accepts your full unsorted, unpaginated items list and a list of DataTableColumn<T> definitions. It handles sorting, optional selection, and Previous/Next pagination internally — pass onSortChange / onSelectionChange if you need to observe.

Each column declares:

  • id — stable identifier used as the sort key and for visibility.
  • header — header label (also used as the accessible label when headerContent is provided).
  • width — fixed Dp width applied to both header and body cells in this column.
  • cell — renders a single body cell from one item.

Sorting

Set sortable = true on a column and supply a comparator. Clicking the header cycles through NONE → ASC → DESC → NONE. The active column shows an up/down arrow; unsorted sortable columns show a UnfoldMore icon. Observe changes with onSortChange, and seed an initial state with initialSort = SortState(columnId = "amount", direction = SortDirection.DESC).

Row selection

Set enableSelection = true and DataTable auto-prepends a checkbox column (selectionColumnWidth = 48.dp by default). The header checkbox toggles all rows on the current page, and onSelectionChange emits the full Set<T> of selected items across all pages. A summary line below the table reads "N of M row(s) selected.".

The underlying Checkbox doesn't support an indeterminate visual, so the header checkbox is "checked" iff every row on the current page is selected. Toggling it adds or removes the current page's rows from the selection set.

Custom pagination

The pagination slot defaults to DefaultPagination — Previous / Next outline buttons aligned to the end. To replace it, pass your own @Composable PaginationScope.() -> Unit. PaginationScope exposes page, pageCount, canPrev, canNext, prev(), next(), and goTo(page) so you can render numbered pages, jump-to controls, or anything else.

API reference

Table

ParameterTypeDefaultDescription
modifierModifierModifierModifier applied to the outer container.
scrollStateScrollStaterememberScrollState()Horizontal scroll state shared by header and body.
content@Composable ColumnScope.() -> UnitTable sections — typically TableHeader, TableBody, and TableFooter.

TableRow

ParameterTypeDefaultDescription
modifierModifierModifierPass Modifier.width(...) to make rows span the same total width under Table's horizontal scroll.
onClick(() -> Unit)?nullOptional click handler. When non-null the row becomes clickable.
selectedBooleanfalseWhen true, the row paints a muted background.
showBottomBorderBooleantrueWhen false, the row omits its bottom border — pass false for the final body row.
content@Composable RowScope.() -> UnitCells of the row — typically TableHead or TableCell.

TableHead / TableCell

Both accept a modifier: Modifier and a content: @Composable RowScope.() -> Unit. Pass Modifier.width(...) to set the column's width — header and body cells in the same column should share the same width.

  • TableHead: 40.dp min-height, 8.dp horizontal padding, medium-weight muted text.
  • TableCell: 8.dp all-around padding, foreground text color.

TableCaption

ParameterTypeDefaultDescription
textStringCaption text.
modifierModifierModifierModifier applied to the caption.

Render TableCaption outside the Table container.

DataTable<T>

ParameterTypeDefaultDescription
itemsList<T>Full unsorted, unpaginated data set.
columnsList<DataTableColumn<T>>Column definitions.
rowKey(T) -> AnyStable key for an item. Used for selection identity.
modifierModifierModifierModifier applied to the outer container.
enableSelectionBooleanfalseWhen true a leading checkbox column is auto-prepended.
selectionColumnWidthDp48.dpWidth of the auto-injected selection column.
pageSizeInt10Rows per page.
initialSortSortStateSortState()Initial sort state.
onSortChange((SortState) -> Unit)?nullObserver fired when sort changes.
onSelectionChange((Set<T>) -> Unit)?nullObserver fired when selection changes.
onRowClick((T) -> Unit)?nullOptional row click handler.
captionString?nullOptional caption text rendered below the table.
pagination@Composable PaginationScope.() -> UnitDefaultPaginationSlot rendered as a full-width row below the summary line.

The summary row above the pagination slot reads "N of M row(s) selected." (when enableSelection = true) on the left and "Page X of Y" on the right.

DataTableColumn<T>

FieldTypeDescription
idStringStable identifier used as the sort key and visibility key.
headerStringHeader label text. Also the accessible label when headerContent is provided.
widthDpFixed width applied to header and body cells in this column.
sortableBoolean = falseWhen true, clicking the header cycles NONE → ASC → DESC → NONE. Requires comparator.
comparatorComparator<T>?Comparator used when sortable is true.
headerContent(@Composable RowScope.() -> Unit)?Optional custom header cell content. When null, header is rendered as a medium-weight muted label.
cell@Composable RowScope.(T) -> UnitRenders one cell for the given row item.

SortState / SortDirection

enum class SortDirection { NONE, ASC, DESC }
 
data class SortState(
    val columnId: String? = null,
    val direction: SortDirection = SortDirection.NONE,
)

PaginationScope

The receiver for the pagination slot. Use it to render any pagination UI you like.

MemberTypeDescription
pageIntCurrent page index (0-based).
pageCountIntTotal number of pages. Always at least 1.
canPrevBooleantrue when prev() will advance backward.
canNextBooleantrue when next() will advance forward.
prev()UnitGo to the previous page if canPrev.
next()UnitGo to the next page if canNext.
goTo(page)UnitJump to an absolute 0-based page index (coerced in range).

The default slot (DefaultPagination) renders Previous / Next outline buttons aligned to the end of a full-width row.

Edit this page on GitHub