@adapttable/unstyled
v2.1.2
Published
Headless, unstyled AdaptTable adapter — semantic HTML with class + data-attribute hooks for Tailwind, shadcn/ui, or your own CSS.
Downloads
1,999
Maintainers
Readme
@adapttable/unstyled
📖 Documentation · 🚀 Live demo · Get started · ⚡ Try in StackBlitz
The headless, unstyled adapter for AdaptTable.
Renders semantic HTML with data-adapttable-part + data-* state hooks
and per-part className overrides — style it with Tailwind, shadcn/ui,
or your own CSS. Ships zero styles.
pnpm add @adapttable/unstyled @adapttable/core react react-domQuickstart
import {
DataTable,
useFrontendData,
type ColumnDef,
} from "@adapttable/unstyled";
const columns: ColumnDef<Person>[] = [
{ key: "name", header: "Name", accessor: (r) => r.name, sortable: true },
{ key: "city", header: "City", accessor: (r) => r.city },
];
export function People({ data }: { data: Person[] }) {
const source = useFrontendData({ data, columns });
return (
<DataTable
source={source}
columns={columns}
rowKey={(r) => r.id}
classNames={{
table: "w-full text-sm",
headerCell: "text-left font-medium text-zinc-500 px-3 py-2",
row: "border-b hover:bg-zinc-50 data-[selected]:bg-blue-50",
cell: "px-3 py-2",
}}
/>
);
}Features
- Automatic mobile cards — below the mobile breakpoint every row renders as a semantic card block (same filters, search, selection and URL state) and infinite scroll replaces the pager; tune per column with
mobileLabel/hideOnMobile, pin either layout withforceMobile. See it flip live. - Client or server data through one
TableSourcecontract — same props either way. - URL-synced search / sort / filters / page — shareable, deep-linkable links.
- Sorting via sortable headers.
- Filtering — a drawer or popover of filters plus removable removable chips, with a filter count on the trigger.
- Selection + bulk actions using plain
<input type="checkbox">, with confirm dialogs (bulkActions). - Row actions with optional confirm,
isHidden/isDisabledper row. - Row expansion — inline detail panels via
renderRowDetail. - Inline cell editing (
onCellEdit+editablecolumns) — text, number and select editors; Enter commits, Escape cancels, Tab moves on. Omit the handler and no cell opens. - Row grouping (
groupBy) with per-group aggregates sharing thesummaryRowmapper. - Column management — show/hide, reorder, pin (sticky) and resize, from a built-in menu.
- Saved views — name a filter/sort/column arrangement and switch between them.
- CSV export (
exportCsv) — current page or the full filtered set. - Virtualization (
virtualize) — opt-in row/card windowing for very large lists. - Pagination — numbered pagination, or infinite scroll (auto by device).
- States — skeleton loading, error with retry, and an empty state.
- RTL via
dir; dark mode is whatever your CSS says. - Customisation —
classNamesfor every part,slots, injectableconfirm— zero opinions about styling, and the full headless escape hatch via@adapttable/core.
Styling hooks
Every node carries:
data-adapttable-part="…"—root,toolbar,search-field,search-icon,search,filters-button,filters-icon,column-menu,table,row,cell,header-cell,sort-button,chips,chip,bulk-bar,footer,empty,loading,error,card, …data-*state —data-selectedon selected rows/cards,data-sorted(asc/desc) on the active header,data-mobileon the root, anddata-density(comfortable/compact) on the root. The adapter ships no density styles — drive spacing yourself, e.g.[data-density="compact"] [data-adapttable-part="cell"] { padding: 4px 8px; }.- A per-part
classNamefrom theclassNamesprop.
Target them with attribute selectors ([data-adapttable-part="row"]),
Tailwind data variants (data-[selected]:bg-blue-50), or class overrides.
The leading search/funnel glyphs render as inline currentColor SVGs in the
search-icon / filters-icon slots, so you can restyle or hide them via the
searchIcon / filtersIcon class names (or the matching data-adapttable-part
selectors). SearchIcon and FiltersIcon are also exported for reuse.
Empty / loading overrides
Replace the empty-state or first-load skeleton with the top-level
emptyState / loadingState props, or with the cross-adapter slots alias —
whichever your other adapters already use:
<DataTable
source={source}
columns={columns}
rowKey={(r) => r.id}
slots={{ empty: <MyEmpty />, skeleton: <MySkeleton /> }}
/>slots.empty / slots.skeleton take precedence when both forms are supplied
(slots.empty ?? emptyState, slots.skeleton ?? loadingState).
Everything else — client/server data, URL state, sorting, filtering,
selection + bulk actions, RTL (dir), auto desktop/mobile — works the same
as the other adapters, on the headless @adapttable/core engine.
See it work
Each clip is the real adapter, recorded on the live demo.
Row grouping — group rows by a column with per-group subtotals

Inline cell editing — double-click a cell; text, number and select editors

Filtering — type a bound and the table answers as you type

Column management — show, hide, reorder, pin and resize

RTL / Arabic — the whole table mirrors, not just the text

Documentation
Getting started · Live demo · Comparison vs ag-Grid · MUI X · TanStack
- Data — client vs server tiers · pagination & infinite scroll · URL-synced state
- Interaction — filtering · sorting · selection & bulk actions · row expansion · inline cell editing
- Columns — show/hide · reorder · pin · resize · row grouping & aggregates · CSV export
- More — i18n & RTL · virtualization · customization · API · FAQ
