Skip to content

Customization

A spectrum, all opt-in: restyle parts with classNames, replace parts with slots, tune the chrome with props, or theme through your kit’s provider.

Restyle without replacing. Mantine and Chakra expose five wrapper hooks (root, toolbar, table, card, footer); the unstyled adapter exposes a hook for every rendered node:

import { DataTable } from "@adapttable/unstyled";
<DataTable
data={data}
columns={columns}
rowKey={(r) => r.id}
classNames={{
table: "w-full text-sm",
row: "border-b hover:bg-zinc-50 data-[selected]:bg-blue-50",
cell: "px-3 py-2",
filtersPopover: "rounded-lg border bg-white shadow-xl",
filtersDone: "rounded-md bg-zinc-900 text-white px-3 py-2",
}}
/>;

Every unstyled node also carries a stable data-adapttable-part attribute — the kebab-case of the classNames key (searchFielddata-adapttable-part="search-field") — plus data-* state attributes, so plain CSS, Tailwind, and shadcn tokens all work. The full part map:

PartElement
rootThe outer wrapper around the whole table.
toolbarThe toolbar row (search, filters, columns, views, your toolbar).
searchFieldThe search field wrapper (input + leading icon).
searchThe search <input>.
searchIconThe leading magnifying-glass icon.
sortSelectThe mobile sort-by <select>.
rowsPerPageSelectRows-per-page <select> (toolbar in infinite mode, footer when paged).
PartElement
filtersButtonThe Filters trigger button.
filtersIconThe funnel icon inside the trigger.
filtersCountThe active-filter count badge.
filtersAnchorThe popover anchor wrapper around the trigger.
filtersPopoverThe anchored popover card (filtersMode="popover").
filtersBackdropThe drawer backdrop (filtersMode="drawer").
filtersPanelThe drawer panel.
filtersHeader / filtersTitle / filtersClosePanel header, its title, and the close button.
filtersBody / filtersFooterThe panel content area and its action row.
filtersClear / filtersDoneThe clear-all and done/apply buttons.
filterField / filterLabelOne auto-built field’s wrapper and its caption.
filterInput / filterSelect / filterOperatorText/date/number inputs, the select widget, and a range field’s operator <select>.
filterCheckboxGroup / filterCheckboxA multiSelect checkbox list and one option.
filterOptionsLoadingThe placeholder shown while async options load.
PartElement
chipsThe active-filter chip strip.
chipOne removable chip.
chipRemoveA chip’s remove button.
PartElement
columnMenu / columnMenuButton / columnMenuPanelThe menu wrapper, trigger, and dropdown panel.
columnMenuHeader / columnMenuTitleThe panel header and its title.
columnMenuItem / columnMenuGrip / columnMenuLabelOne column row, its drag grip, and its label.
columnMenuVisibility / columnMenuPinThe show/hide toggle and the pin control.
columnMenuSeparator / columnMenuResetThe separator above the actions entry and the reset button.
resizeHandleA header’s drag/keyboard resize handle.
PartElement
viewsButton / viewsPanelThe menu trigger and dropdown panel.
viewsItem / viewsDeleteOne view’s apply button and its delete button.
viewsInput / viewsSaveThe view-name input and the save button.
PartElement
bulkBar / bulkButtonThe selection toolbar and one bulk-action button.
selectAllBanner / selectAllText / selectAllButtonThe cross-page banner, its status text, and its action.
selectionCell / checkboxA row’s selection cell and the checkbox itself.
PartElement
table / thead / tbodyThe <table> and its sections.
headerRow / headerCellThe header <tr> and one <th>.
groupRow / groupCellThe grouped-header row and one spanning cell.
sortButton / sortIndexA sortable header’s button and its multi-sort badge.
row / cellOne body <tr> and one <td>.
actionsCell / actionButtonThe trailing actions cell and one action button.
PartElement
expandHeader / expandCellThe leading chevron header cell and body cell.
expandButtonThe expand/collapse chevron (rows and cards).
detailRow / detailCellThe full-width detail <tr> and its spanning <td>.
cardDetailThe detail section inside an expanded mobile card.
PartElement
cards / cardThe card list and one card.
cardRow / cardLabel / cardValueOne label/value line inside a card.
summary / summaryRow / summaryCellThe <tfoot>, its <tr>, and one summary <td>.
summaryCardThe trailing summary card in the mobile list.
PartElement
footer / pageButtonThe footer bar and the prev/next/numbered page buttons.
loadMore / loadMoreButtonThe infinite-mode sentinel area and its button.
empty / emptyClearThe empty state and its clear-filters button.
loading / refreshIndicatorThe first-load skeleton and the background-refresh bar.
error / retryButtonThe error state and its retry button.

A few purely structural nodes (scroll-box, virtual-spacer, the skeleton internals) expose only the data-adapttable-part attribute.

Replace whole sub-components on any adapter:

<DataTable
data={data}
columns={columns}
rowKey={(r) => r.id}
slots={{ empty: <MyEmptyState />, skeleton: <MySkeleton /> }}
/>

skeleton replaces the first-load skeleton; empty replaces the empty-state. The error state is built-in (retry button included) — translate it via the errorTitle / errorMessage / retry labels. The unstyled adapter also accepts the equivalent top-level emptyState / loadingState props (the slots entry wins when both are set).

<DataTable
data={data}
columns={columns}
rowKey={(r) => r.id}
density="compact"
/>

"comfortable" (default) is the roomy layout; "compact" tightens row height and padding. Each adapter maps it to its kit’s table size — MUI "comfortable"medium / "compact"small, antd → middle / small — and MUI, Chakra, and antd offer an explicit size prop that overrides the mapping (e.g. antd size="large").

<DataTable
data={data}
columns={columns}
rowKey={(r) => r.id}
stickyHeader // keep the desktop header pinned while the page scrolls
stickyTop={56} // offset under your app header (also offsets the toolbar)
maxHeight={420} // fixed-height scroll box instead of page scroll
/>

maxHeight turns the table into a scroll box that also scrolls sideways — the header and pinned columns stick within it, which is what makes column pinning visibly stick. scrollToTopOnChange (default true) scrolls back to the table when search/filter/page changes, with scrollTopGap (default 8) of breathing room below sticky chrome.

The core is style-free: wrap your app in the kit’s provider and AdaptTable renders with that kit’s real components, following its theme and dark mode (prefers-color-scheme) automatically. Kit-specific knobs:

  • Mantineanimate enables a dependency-free row/card entrance stagger (honours reduced motion). Rolling your own? Every animatable row/card carries a data-stagger attribute — leave animate off and target those elements with GSAP/Framer Motion.
  • MUIsize ("small" | "medium") overrides the density mapping; className lands on the root <Paper>.
  • ChakracolorScheme colors primary accents (buttons, badges); size ("sm" | "md" | "lg", default "md").
  • Ant Designsize ("small" | "middle" | "large"), bordered for cell borders, virtualHeight / virtualWidth for the virtualized scroll area, className on the wrapper.
  • Unstyled — no provider needed; theme entirely through classNames, the data-adapttable-part hooks above, and your own CSS variables / data-theme.

Pass a Cell component (receives { row, rowIndex }; define it at module level so its identity is stable) or a lighter accessor:

import type { CellProps } from "@adapttable/core";
function StatusCell({ row }: CellProps<Person>) {
return (
<Badge color={row.status === "active" ? "green" : "gray"}>
{row.status}
</Badge>
);
}
const columns = [
{ key: "name", sortable: true },
{ key: "status", Cell: StatusCell },
{
key: "salary",
accessor: (r: Person) => formatMoney(r.salary),
align: "end",
},
];

See the Columns guide and the ColumnDef table for the full column surface (sortValue, i18n, group, hideOnMobile, …).