mds-datatable-v2
v0.2.6
Published
Production-ready headless React DataTable built on TanStack Table. Fully controlled, accessible, and framework-agnostic styling.
Maintainers
Readme
mds-datatable
Production-ready headless React DataTable built on @tanstack/react-table v8.
Designed for enterprise apps (ERP, CRM, Hotel PMS, Inventory, Admin Dashboards, SaaS). The library handles rendering, interactions, keyboard navigation, accessibility, drag reorder, and resize — and never stores business state.
Principles
- Fully controlled — sorting, filters, pagination, visibility, order, sizing, pinning, expansion, and selection are owned by the consumer
- Headless styling — plain HTML + CSS class hooks; no Material, Chakra, Ant, Mantine, Bootstrap, Tailwind, or Radix
- Accessible — ARIA grid semantics, keyboard navigation, focus management
- Composable — render props for header, cell, footer, toolbar, loading, empty, and error states
- Publishable — ESM + CJS + TypeScript declarations via
tsup
Install
npm install mds-datatable @tanstack/react-table
# peer: react@^18 || ^19, react-dom@^18 || ^19import { DataTable } from 'mds-datatable';
import 'mds-datatable/styles.css';Quick start
import { useState } from 'react';
import {
createColumnHelper,
type SortingState,
type ColumnSizingState,
} from '@tanstack/react-table';
import { DataTable } from 'mds-datatable';
import 'mds-datatable/styles.css';
interface User {
id: string;
name: string;
age: number;
}
const columnHelper = createColumnHelper<User>();
const columns = [
columnHelper.accessor('name', { header: 'Name' }),
columnHelper.accessor('age', { header: 'Age' }),
];
export function UsersTable({ data }: { data: User[] }) {
const [sorting, setSorting] = useState<SortingState>([]);
const [columnSizing, setColumnSizing] = useState<ColumnSizingState>({});
return (
<DataTable
data={data}
columns={columns}
getRowId={(row) => row.id}
sorting={sorting}
onSortingChange={setSorting}
columnSizing={columnSizing}
onColumnSizingChange={setColumnSizing}
enableRowSelection
aria-label="Users"
/>
);
}When a user resizes a column, the library calls onColumnSizingChange(next). Your app decides whether to persist to an API, database, Redux, Zustand, or localStorage. The library does nothing else.
Controlled state API
| State | Prop | Callback |
| --- | --- | --- |
| Sorting | sorting | onSortingChange |
| Pagination | pagination | onPaginationChange |
| Column filters | columnFilters | onColumnFiltersChange |
| Global filter | globalFilter | onGlobalFilterChange |
| Visibility | columnVisibility | onColumnVisibilityChange |
| Order | columnOrder | onColumnOrderChange |
| Width | columnSizing | onColumnSizingChange |
| Pinning | columnPinning | onColumnPinningChange |
| Expanded | expanded | onExpandedChange |
| Selection | rowSelection | onRowSelectionChange |
Features
Columns
- Sorting (multi-sort with Shift)
- Resizing + double-click auto-fit
- Drag reorder (
@dnd-kit) - Hide/show via
columnVisibility - Pin left / pin right
- Grouped & nested headers (TanStack column groups)
- Custom header / cell / footer renderers
Rows
- Click, double-click, context menu
- Hover callback
- Selection with Shift-range and Ctrl/Meta-toggle
- Expandable rows +
renderExpandedRow
Keyboard
Arrow keys, Home/End, Page Up/Down, Tab, Enter, Escape, Ctrl/Cmd+A, Space — with focus management.
Loading / empty / error
<DataTable
loading
loadingMode="overlay" // 'overlay' | 'skeleton' | 'replace'
error={null}
emptyMessage="No records"
renderLoading={() => <Spinner />}
renderEmpty={() => <EmptyIllustration />}
renderError={(err) => <ErrorPanel error={err} />}
/>Optional header
<DataTable showHeader={false} ... />Styling
Stable class names (never theme-locked):
datatable
datatable-table
datatable-header
datatable-header-cell
datatable-body
datatable-row
datatable-cell
datatable-selected
datatable-hover
datatable-resize-handle
datatable-dragging
datatable-loading
datatable-empty
datatable-errorOverride CSS variables:
.datatable {
--datatable-border-color: #ccc;
--datatable-header-bg: #fafafa;
--datatable-row-selected-bg: #e8f1ff;
--datatable-focus-ring: #2563eb;
}Every major surface accepts className / style.
Architecture
src/
components/ DataTable, Header, Body, Row, Cell, ResizeHandle, DragLayer, Footer, Overlay
hooks/ useDataTable, keyboard, selection, reorder, resize
types/ public props & class-name contracts
utils/ pinning styles, autofit measure, class helpers
styles/ structural CSS onlyVirtualization is intentionally not coupled into core. The row model and render boundaries stay compatible with a future @tanstack/react-virtual adapter.
Scripts
npm run build # tsup + copy CSS
npm run typecheck # strict TypeScript
npm run test # vitest
npm run lint # eslint
npm run docs # typedocPeer dependencies
react^18 || ^19react-dom^18 || ^19@tanstack/react-table^8.21
License
MIT
