Headless engine · native UI-kit adapters

One headless engine.
Every UI kit.

Headless freedom, batteries included for your kit. The same table features render natively in Mantine, MUI, Chakra, Ant Design, Radix, Base UI, shadcn/Tailwind — or your own custom UI. URL state, RTL, and a real filter UX, out of the box.

npx @adapttable/cli init
Try the live demoOpen StackBlitz
Watch it switch

One table. Eight UI kits.

The same dataset and feature set, re-rendered by each real adapter — Mantine, MUI, Chakra, Ant Design, Radix, Base UI, shadcn and Tailwind. Nothing about the table changes but its skin.

One dataset, one feature set, every kit —open the live demo
The batteries

Everything a data table owes you.

Columns & filters

One declaration drives the widget, the URL params, the chips and the row predicate. Operator-first number and date filters: Equal, At least, At most, Between.

Three data tiers

Hand the table an array and it filters, sorts and pages in memory. Or own the fetch: one consolidated query event with an abort signal. Or bring a full custom TableSource.

URL-synced everything

Search, sort, filters, pagination, column layout and saved views live in shareable URLs — namespaced per table, SSR-safe, with a one-prop opt-out (urlSync={false}).

50,000 rows, no flinching

Opt-in virtualization windows the DOM against the page or any max-height scroll box. The live demo scrolls fifty thousand rows with a handful of DOM nodes.

i18n & RTL, for real

Label presets for 18 locales. Columns map per-locale data paths, so cells, sorting and filtering follow the language. Pinning and layout are logical — Arabic and Hebrew just work.

Column management

Show, hide, reorder, pin and resize — the row-actions column included. Capture any state as a named saved view and restore it later, per table.

Selection & bulk actions

Select the page — or all matches across every page. Bulk actions run through an injectable confirm, and per-row actions explain themselves with disabledReason.

Accessible by default

Real table roles, aria-sort, focus management and full keyboard navigation — column reorder and resize included, straight from the keyboard.

AI assistant

Ask the table in plain words.

An opt-in assistant drives the same governed actions a reader has — filter, group, aggregate, hide and reorder columns. Writes wait for approval by default, and @adapttable/aiships no model SDK: you connect the model you run.

Asking the table to filter, group, aggregate, hide and reorder columns — every change lands through the table's own actions.
Customization spectrum

Easy when you want. Headless when you need.

Start with props and grow all the way to prop-getters — at no point do you hit a wall and have to eject.

Customization guide
01

Props

Pass data, columns and a row key. Sensible defaults do the rest.

<DataTable data={rows} columns={cols}
  rowKey={(r) => r.id} />
02

Slots

Swap the empty, no-results, error and skeleton states.

slots={{ empty: <NoResults/>,
  skeleton: <Shimmer/> }}
03

classNames + data-*

Style the kit's parts; target rows by state.

classNames={{ table: 'rounded-xl' }}
// [data-selected] [data-pinned]
04

Custom toolbar

Add your own toolbar controls and confirm dialog.

toolbarSlots={{ start: <Title/> }}
confirm={myConfirm}
05

Fully headless

Prop-getters. You own every element.

const { getRowProps,
  getHeaderCellProps } = useDataTable(opts)
EasyPro
Get started

A few lines to your first table.

The CLI detects your UI kit and scaffolds a working table; the docs cover every prop, the three data tiers, theming, i18n/RTL and the TanStack comparison.

import { DataTable } from "@adapttable/mantine"; // or mui, chakra, antd, radix, base-ui, shadcn, unstyled
import { filters } from "@adapttable/mantine/filters";

<DataTable
  data={people}
  rowKey={(person) => person.id}
  columns={[
    { key: "name" },
    { key: "team", filter: "multiSelect" },
    { key: "hiredAt", filter: "dateRange" },
  ]}
  features={[filters([])]}
/>
npx @adapttable/cli init