This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
A Compose Multiplatform library implementing the Material Design data table spec. Originally derived from the table implementation removed from Compose pre-1.0. Published to Maven Central as com.seanproctor:datatable and com.seanproctor:datatable-material3. Library targets: Android, JVM (desktop), iOS (arm64/simulatorArm64), JS, and Wasm/JS.
Requires JDK 17+ to build (enforced in settings.gradle.kts). Built with Android Gradle Plugin 9 (built-in Kotlin) and Gradle 9.
Library:
:datatable— Core, unstyled implementation. Depends only oncompose.foundation. This is where the layout, measurement, sorting, scrolling, and pagination logic lives.:datatable-material3— Thin Material 3 styling layer.api-depends on:datatableand providesDataTable/PaginatedDataTablewrappers that supply aMaterial3CellContentProviderplus M3 defaults (dividers, 56dp header / 52dp rows, typography).
Demo (under demo/, all consume the library):
:demo:shared— KMP library (com.android.kotlin.multiplatform.library) holding the sharedApp()composable (commonMain) and the iOSMainViewController(iosMain, frameworkbaseName = "DemoApp"). Targets android/jvm/wasmJs/iosArm64/iosSimulatorArm64.:demo:androidApp—com.android.application(MainActivity + manifest), depends on:demo:shared.:demo:desktopApp—kotlin("jvm")+ Compose Desktop (Main.kt), depends on:demo:shared.:demo:webApp— KMP wasmJs-only browser app (main.kt+index.html), depends on:demo:shared.demo/iosApp— Xcode project (not a Gradle module). Links theDemoAppframework via a build phase that runs:demo:shared:embedAndSignAppleFrameworkForXcode; framework search path is$(SRCROOT)/../shared/build/....
The demo is split into per-platform app modules because AGP 9 no longer allows com.android.application to coexist with org.jetbrains.kotlin.multiplatform in one module — the Android app must depend on a separate KMP library module (:demo:shared).
build-logic— Included build with convention plugins (see below). Not a regular subproject.
The core abstraction layers, from low to high:
BasicDataTable/BasicPaginatedDataTable(in:datatable) — The unstyled engine.BasicDataTableuses aSubcomposeLayoutto measure rows/columns and render header, body, and footer slots. All sizing, alignment, scrolling, and sort-icon plumbing happens here.CellContentProvider— The styling seam between core and Material 3.BasicDataTabledelegates rendering of each cell (RowCellContent) and header (HeaderCellContent, including the sort UI) to an injected provider.DefaultCellContentProviderrenders content as-is;Material3CellContentProviderwraps it with M3 typography and sort icon buttons. To change how cells/headers look without touching layout, implement aCellContentProvider.DataTable/PaginatedDataTable(in:datatable-material3) — Public convenience wrappers that inject the M3 provider and defaults.
Key pieces:
- DSL (
DataTableDsl.kt) —DataTableScope.row { ... }buildsTableRowScopeImpl, andTableRowScope.cell { ... }collects cell composables. Rows carryonClick,height,isHeader,isFooter,backgroundColor. The content lambda runs eagerly to collect rows before layout. - Columns (
DataColumn.kt) — Each column has analignment, aTableColumnWidth, an optionalonSortcallback, and a header composable. - Column widths (
TableColumnWidth.kt) — Sealed hierarchy:Flex,Fixed,Fraction,Wrap,MinIntrinsic,MaxIntrinsic, plus combinatorsMin/Maxand.flexible(flex). Drives the measurement pass. - State —
DataTableState(+rememberDataTableState) holds scroll state.PaginatedDataTableState(+rememberPaginatedDataTableState) addscount,pageIndex, andPageSize(FixedSizeorFillMaxHeight); the paginated content lambda receives(fromIndex, toIndex). - Sorting — Driven by
sortColumnIndex/sortAscendingparams plus per-columnonSort. The provider renders the sort icon;isSortIconTrailingcontrols icon placement.
Library modules apply two convention plugins from build-logic rather than configuring KMP/publishing inline:
datatable.library(LibraryConventionPlugin) — Applies the KMP, Android library, and Compose plugins and configures all targets viaconfigureKotlinMultiplatform(KotlinAndroid.kt):jvmToolchain(11), AndroidminSdk=21/compileSdk=36, namespace derived from module name, plus jvm/js/wasmJs/iOS (arm64 + simulatorArm64) targets.datatable.publish(PublishConventionPlugin) — Vanniktech Maven publish to Maven Central, signing, and POM metadata.
When adding a new published library module, apply both plugins (see datatable/build.gradle.kts) and add it to settings.gradle.kts. The demo modules do not use these plugins (they configure KMP/Android inline and are not published).
AGP 9 note: the com.android.kotlin.multiplatform.library Android target is configured via the androidLibrary { } DSL in .kts build files (:demo:shared). The precompiled convention plugin can't use that generated accessor, so KotlinAndroid.kt configures it through the named extension instead: extensions.configure<KotlinMultiplatformAndroidLibraryTarget>("androidLibrary") { ... }. The Android app module uses AGP 9's built-in Kotlin (no kotlin.android plugin — applying it is an error under AGP 9).
Dependencies are managed in gradle/libs.versions.toml (version catalog). All plugin versions are declared once in the root build.gradle.kts plugins { ... apply false } block; modules then apply them via alias(...) without a version. The project version is set by datatable.version in gradle.properties.
# Build everything
./gradlew build
# Run the demo on desktop (JVM)
./gradlew :demo:desktopApp:run
# Run the demo in the browser (Wasm/JS)
./gradlew :demo:webApp:wasmJsBrowserDevelopmentRun
# Build the Android demo APK / install on a device
./gradlew :demo:androidApp:assembleDebug
./gradlew :demo:androidApp:installDebug
# Desktop hot reload
./gradlew :demo:desktopApp:hotRun
# Run all checks / tests across targets
./gradlew check
./gradlew allTests
# Publish a local test repo (to ./build/testMaven of each module)
./gradlew publishAllPublicationsToTestMavenRepositoryThere is currently no unit/instrumented test source set in the library modules.
Releases are automated: creating a GitHub Release triggers .github/workflows/build.yml, which runs ./gradlew build then ./gradlew publishAndReleaseToMavenCentral on macos-latest (needed for iOS targets). Maven Central and GPG signing credentials come from repository secrets / ~/.gradle/gradle.properties.
- Kotlin official code style. Import ordering and star-import thresholds are pinned in
.editorconfig(*,java.**,javax.**,kotlin.**,^, no star imports). License/copyright templates live inspotless/. - Files derived from AOSP retain the Apache 2.0 header; new files in those modules should keep it consistent.