Data Table
Sorting with Shift multi-sort, search, faceted filters, range selection, pages or load more. No table library.
Built on Button, Checkbox, Dropdown Menu, Icons, Input, Pagination, Skeleton and Table.
| INV-24000 | Ferro Robotics | Open | Team | $1,393.59 | Oct 12, 2026 | |
|---|---|---|---|---|---|---|
| INV-23999 | Ferro Robotics | Open | Starter | $371.45 | Oct 12, 2026 | |
| INV-23998 | Orbital Freight | Open | Starter | $339.81 | Oct 12, 2026 | |
| INV-23997 | Parallel Works | Open | Starter | $289.60 | Oct 12, 2026 | |
| INV-23996 | Northwind Mobility | Draft | Starter | $232.30 | Oct 11, 2026 | |
| INV-23995 | Moss & Pike | Open | Starter | $397.89 | Oct 11, 2026 | |
| INV-23994 | Sable Systems | Draft | Enterprise | $17,842.46 | Oct 11, 2026 | |
| INV-23993 | Ferro Robotics | Open | Starter | $378.59 | Oct 10, 2026 | |
| INV-23992 | Orbital Freight | Draft | Starter | $239.10 | Oct 10, 2026 | |
| INV-23991 | Juniper Bank | Draft | Starter | $300.71 | Oct 10, 2026 |
1–10 of 1,000
Installation
npx shadcn@latest add https://neonkit.heyaadi.com/r/data-table.jsonUsage
import { DataTable, type DataTableColumn } from "@/components/ui/data-table"
const columns: DataTableColumn<Invoice>[] = [
{ id: "id", header: "Invoice", accessor: "id", rowHeader: true },
{ id: "status", header: "Status", accessor: "status", sortable: true, filter: "select" },
{ id: "amount", header: "Amount", accessor: "amount", sortable: true, align: "right" },
]
<DataTable label="Invoices" data={invoices} columns={columns} selectable />Examples
Loading and empty
Skeleton rows while loading, and your own empty state when there's nothing yet.
Linked rows, load more
getRowHref makes each row a real link. Compact density, a text filter and a growing list instead of pages.
Headless
useDataTable runs the same sorting, search and selection behind your own markup.
API reference
DataTable
Everything in useDataTable, plus the UI. Built on Table, Checkbox, Input, Button, Dropdown Menu, Pagination and Skeleton. Accepts every native div attribute.
| Prop | Type | Default |
|---|---|---|
data | T[]The rows. Keep the array stable between renders; sorting and search are memoized on it. | None |
columns | DataTableColumn<T>[]Column definitions; see below. | None |
label | stringAccessible name for the table. Use it or caption. | None |
caption | ReactNodeA visible caption under the table. | None |
getRowId | (row: T, index: number) => stringStable id for selection. Falls back to the index. | row.id |
selectable | booleanAdds the checkbox column. Shift-click or Shift+Space ticks a range. | false |
selectionActions | (rows: T[], clear: () => void) => ReactNodeBulk actions shown in the toolbar while rows are selected. | None |
onRowClick | (row: T) => voidMakes rows focusable and clickable; Enter and Space work too. Clicks on controls inside a row don't count. | None |
getRowHref | (row: T) => stringWraps the row header in a link stretched over the whole row. | None |
getRowLabel | (row: T) => stringNames a row for its checkbox. Defaults to the row header's text. | None |
pagination | "pages"Numbered pages, a growing list, or every row. | "pages" |
pageSizeOptions | number[]Adds a rows-per-page menu. | None |
loading | booleanSkeleton rows and aria-busy. | false |
emptyState | ReactNodeShown when there's no data. Filtered-out results get their own message with a reset. | None |
stickyHeader | booleanPins the header while the body scrolls. | true |
maxHeight | numberCaps the table's height so it scrolls under the header. | None |
searchable | booleanShows the search field. | true |
searchPlaceholder | stringPlaceholder for the search field. | "Search…" |
toolbar | ReactNodeExtra controls at the end of the toolbar. | None |
hideToolbar | booleanRemoves the toolbar. | false |
noun | [string, string]Singular and plural for counts and messages. | ["row", "rows"] |
color | NeonColorPalette name or hex. Falls back to --neon-accent, then cyan. | inherited |
DataTableColumn<T>
One column.
| Prop | Type | Default |
|---|---|---|
id | stringUnique key, used by sorting, filters and visibility. | None |
header | ReactNodeHeader content. | None |
accessor | keyof TThe value that sorting, search and the default cell read. | None |
cell | (ctx: { row, value, index, highlight }) => ReactNodeCustom cell. highlight(text) lights the search match. | None |
sortable | booleanMakes the header a sort button. | None |
sortingFn | (a: T, b: T) => numberCustom ascending compare. The default is natural order, with blanks always last. | None |
sortDescFirst | booleanThe first click sorts descending, for dates and amounts. | None |
align | "left"Cell and header alignment. Right-aligned cells get tabular figures. | "left" |
width | numberColumn width. | None |
filter | "text"Adds a filter for this column to the toolbar. | None |
filterOptions | { value: string; label?: ReactNode }[]Choices for a select filter. Defaults to the distinct values, with counts. | None |
filterValue | (row: T) => stringText that search and text filters match. | None |
searchable | booleanFalse leaves the column out of the search. | true |
rowHeader | booleanRenders as th scope="row" and names the row. | None |
hideable | booleanListed in the View menu. | true |
label | stringName in menus when header isn't text. | None |
useDataTable(options)
The state behind DataTable. Every piece is controlled or uncontrolled: sorting, globalFilter, columnFilters, selection, page, pageSize, columnVisibility and density, each with default… and on…Change.
| Prop | Type | Default |
|---|---|---|
rows | DataTableRow<T>[]The current page, or every loaded row with load more. | None |
filteredRows | DataTableRow<T>[]Every row that passes search and filters, sorted. | None |
columns | DataTableColumn<T>[]Visible columns. | None |
toggleSort(id, multi?) | functionOff, first direction, the other, off. multi adds or updates a key instead of replacing. | None |
getSort(id) | { desc, priority }A column's sort state. | None |
setGlobalFilter / setColumnFilter / resetFilters | functionFilter controls. Each goes back to page 1. | None |
toggleRow(id, checked, range?) | functionTicks a row; range ticks everything since the last one. | None |
setRowsSelected / clearSelection / isSelected | functionSelection helpers. | None |
page, pageCount, setPage, setPageSize, loadMore | Pagination. | None |
toggleColumnVisibility(id, visible?) | functionShows or hides a column. | None |
Accessibility
- Sort headers are buttons, so Enter and Space sort and Shift with either adds a sort key. The first key sets aria-sort; later keys say their priority to screen readers.
- Selection counts and search results are announced through polite live regions.
- Sorting and filtering are memoized and only the current page renders, so 1,000 rows sort in a few milliseconds.
- Search runs on a deferred value, so typing stays smooth on large tables.
- Selected rows keep their selection when filtered out; the footer says how many are hidden.
- On narrow screens cells stay on one line and the table scrolls sideways inside its frame.