Data table
A table of records on TanStack Table v9 — sort, filters and facets, groups, details, pages, selection, column visibility, pinning and resizing — in the browser or on the server, cards on a phone, windowing for long lists.
Web uniquement — React, depuis @krizaka/ui/data-table. Bêta : son API peut encore changer à une version mineure.
Pourquoi il est là. Used by Orochia, Orazaka, krizaka.com.
Quand l’utiliser
- To compare records on the same columns: jobs, videos of a studio, users of an admin, invoices, payouts.
- When people sort, filter, group, select several rows for a bulk action, or export what they see.
- In server mode (
manual+onStateChange) when the list lives in a database: the API sorts, filters and pages. - With
virtualizedfor a long list loaded at once (logs, 50,000 events) that must stay smooth.
Quand ne pas l’utiliser
Installation
Installer
npm install @krizaka/ui @krizaka/tailwind @krizaka/tokens tailwindcssStyles
@import "tailwindcss";
@import "@krizaka/tailwind";
@import "@krizaka/ui/tailwind.css";Importer
import { DataTable, DataTableFilters, DataTableColumnsMenu, DataTableExport, useDataTable } from "@krizaka/ui/data-table";Exemples
Jobs
Sortable columns, a status badge, row selection for a bulk action.
| Status | ||||
|---|---|---|---|---|
Filters, columns, export
A search, facets with their counts, a range; the filters as chips; hide columns; export the rows shown as CSV.
36 rows
| Creator | Visibility | ||
|---|---|---|---|
Server mode
Sort, filters and page go to the API through krizakaFetch and defineKeys of @krizaka/query; the previous page stays while the next one loads.
0 rows
| Status |
|---|
Pages
Client pagination under the table, with the rows per page to choose.
Groups
groupBy: a row per group with its count and its total (aggregate), opened to see its rows.
| Creator | From | Amount |
|---|---|---|
Details
renderDetail: a button per row opens a panel under it.
| Details | Creator | Request | Status |
|---|---|---|---|
Pinned and resizable
A wide table: the name pinned at the start, columns resized by drag or by the arrow keys.
| Model | Provider | Input / 1M | Output / 1M | Context | Latency | Region |
|---|---|---|---|---|---|---|
| Llama 3.3 70B | Self-hosted | $0.23 | $0.40 | 128k | 420 ms | eu-west |
| Mistral Large | Mistral | $2.00 | $6.00 | 128k | 380 ms | eu-west |
| Qwen 2.5 Coder | Self-hosted | $0.12 | $0.20 | 32k | 210 ms | eu-central |
Cards on a phone
layout="cards": each row a card, the title on top — what auto does in a narrow container.
| Title | Visibility | Views | Tips |
|---|---|---|---|
Loading
Skeleton rows while the first page loads, the table busy.
| User | Role | Joined |
|---|
Empty
No record yet: an empty state with the next step.
| Invoice | Amount |
|---|---|
Error
The request failed: an alert with a retry, in place of the rows.
| Event | At |
|---|---|
50,000 rows
virtualized: only the rows in view are rendered and measured, the header stays.
| At | ||
|---|---|---|
| evt-1 | view | 2026-10-01 00:00 |
| evt-2 | tip | 2026-10-02 01:00 |
| evt-3 | follow | 2026-10-03 02:00 |
| evt-4 | comment | 2026-10-04 03:00 |
| evt-5 | view | 2026-10-05 04:00 |
| evt-6 | tip | 2026-10-06 05:00 |
| evt-7 | follow | 2026-10-07 06:00 |
| evt-8 | comment | 2026-10-08 07:00 |
| evt-9 | view | 2026-10-09 08:00 |
| evt-10 | tip | 2026-10-10 09:00 |
| evt-11 | follow | 2026-10-11 10:00 |
| evt-12 | comment | 2026-10-01 11:00 |
| evt-13 | view | 2026-10-02 12:00 |
| evt-14 | tip | 2026-10-03 13:00 |
| evt-15 | follow | 2026-10-04 14:00 |
Props
DataTable
A table of records — sort, filters and facets, groups, details, pages, selection, visibility, pinning, resizing — on the client or the server, cards on a phone, windowing for long lists.
| Prop | Type | Défaut | Description |
|---|---|---|---|
captionrequis | string | — | What the table lists ("Jobs", "Your videos"): its <caption>, hidden unless showCaption. |
columnsrequis | readonly DataTableColumn<T>[] | — | The columns, in order (stable: module scope or useMemo). |
getRowIdrequis | (row: T) => string | — | A stable id per row: the React key, the selection's value, the expansion's key. |
rowsrequis | readonly T[] | — | The rows (stable: module scope, state or a query's data). Sorted, filtered and paginated here unless manual. |
className | string | — | Classes merged on the root. |
columnVisibility | Readonly<Record<string, boolean>> | — | Visible (true) or hidden (false) columns, by id (controlled); a column not listed is visible. |
defaultColumnVisibility | Readonly<Record<string, boolean>> | {} | The visibility at first, uncontrolled. |
defaultExpanded | Readonly<Record<string, boolean>> | {} | The opened rows at first, uncontrolled. |
defaultFilters | Readonly<Record<string, FilterValue>> | {} | The filters at first, uncontrolled. |
defaultGroupBy | readonly string[] | [] | The grouping at first, uncontrolled. |
defaultPage | number | 1 | The page at first, uncontrolled. Default 1. |
defaultPageSize | number | — | Rows per page at first, uncontrolled: turns on the pagination under the table. |
defaultSelected | readonly string[] | [] | The ids selected at first, uncontrolled. |
defaultSort | DataTableSort | null | The sort at first, uncontrolled. |
density | "comfortable" | "compact" | — | comfortable (default) · compact. |
empty | ReactNode | — | Shown when there is no row (an EmptyState). Default: labels.empty. |
error | ReactNode | — | A failure, shown in place of the rows (an Alert, a retry button); announced. |
expanded | Readonly<Record<string, boolean>> | — | The opened rows and groups (controlled), by id. |
filters | Readonly<Record<string, FilterValue>> | — | The filters in force (controlled), by column id — what DataTableFilters edits. |
footer | ReactNode | — | Under the table (after the pagination): a total, a note. |
getSubRows | ((row: T) => readonly T[]) | — | The children of a row (a tree): each row with children gets a button that shows them. |
groupBy | readonly string[] | — | The columns the rows are grouped by, in order (controlled) — each groupable. |
labels | Partial<DataTableLabels> | — | The words, passed translated; English by default. |
layout | "table" | "auto" | "cards" | auto | auto (default): a table, cards below 36rem of container width. table or cards to force one. |
loading | boolean | — | Rows are loading: skeleton rows (loadingRows of them) and aria-busy. |
loadingRows | number | 5 | How many skeleton rows while loading. Default 5. |
manual | { sorting?: boolean; filtering?: boolean; pagination?: boolean | undefined; } | undefined | — | The server does the work: the rows are shown as given (sorted, filtered, a single page) — fetch in onStateChange. |
onColumnVisibilityChange | ((visibility: Record<string, boolean>) => void) | — | Called with the new visibility (DataTableColumnsMenu). |
onExpandedChange | ((expanded: Record<string, boolean>) => void) | — | Called with the opened rows. |
onFiltersChange | ((filters: Readonly<Record<string, FilterValue>>) => void) | — | Called with the new filters. |
onGroupByChange | ((groupBy: string[]) => void) | — | Called with the new grouping. |
onPageChange | ((page: number) => void) | — | Called with the page asked for. |
onPageSizeChange | ((pageSize: number) => void) | — | Called with the size chosen in the pagination's select (pageSizeOptions). |
onSelectedChange | ((selected: string[]) => void) | — | Called with the new selection. |
onSortChange | ((sort: DataTableSort) => void) | — | Called when a header asks for a new sort. |
onStateChange | ((query: DataTableQuery) => void) | — | Called with the sort, the filters and the page after each change: the query key of a server table. |
page | number | — | The page, from 1 (controlled). |
pageSize | number | — | Rows per page (controlled): turns on the pagination under the table. |
pageSizeOptions | readonly number[] | — | The sizes the pagination offers ([10, 25, 50]). |
pinning | { start?: readonly string[]; end?: readonly string[]; } | undefined | — | Columns that stay in view while the table scrolls sideways: at the start (a name) or the end (actions). |
renderDetail | ((row: T) => ReactNode) | — | A panel under an opened row (the details of a job): each row gets a button that opens it. |
resizing | boolean | — | Columns can be resized (pointer drag, or the arrow keys on the focused handle). |
rowCount | number | — | The number of rows on the server (manual.pagination): the page count and the results' count. |
rowLabel | ((row: T) => string) | — | The name of a row in its labels (checkbox, expand button). Default: its id. |
selectable | boolean | — | Adds a checkbox per row and one in the header. |
selected | readonly string[] | — | The ids selected (controlled). |
showCaption | boolean | — | Shows the caption above the table. |
sort | DataTableSort | — | The sorted column (controlled), null for none. |
toolbar | ReactNode | — | Above the table, inside its frame: DataTableFilters, DataTableColumnsMenu, DataTableExport, actions. |
virtualized | { height: number; estimateRowHeight?: number; overscan?: number; } | undefined | — | Renders only the rows in view, in a scrolling body of height px; rows are measured (estimateRowHeight is a guess). Forces the table layout. |
DataTableColumnsMenu
A menu of the hideable columns, each a checkbox: the person chooses what the table shows.
| Prop | Type | Défaut | Description |
|---|---|---|---|
className | string | — | Classes merged on the button. |
defaultOpen | boolean | — | Opens the menu at first (a story, a test). |
DataTableExport
Downloads what the table shows — the filtered and sorted rows of every page, the visible columns — as CSV.
| Prop | Type | Défaut | Description |
|---|---|---|---|
className | string | — | Classes merged on the button. |
filename | string | export.csv | The file's name. Default "export.csv". |
onExport | ((csv: string) => void) | — | Receives the CSV instead of downloading it (send it to the server, a test). |
DataTableFilters
The filters of the table, from its columns: a search per text column, a menu of values (with their counts) per
facet column, two bounds per range column; the filters in force as removable chips, "clear all", and the count
of results, announced.
| Prop | Type | Défaut | Description |
|---|---|---|---|
children | ReactNode | — | More controls in the bar (a date range picker, a toggle). |
className | string | — | Classes merged on the bar. |
debounce | number | 250 | The search's wait after the last key, in ms, before the filter applies (a request, in server mode). Default 250. |
Accessibilité
- A
tablewith acaption(hidden unlessshowCaption) andscope="col"headers; the roles are written out, so the cards keep the table's semantics. - The sorted column carries
aria-sort; a sortable header is a button that says its state witharia-pressed. - An expand button (a row's details, a group, a tree) says its state with
aria-expandedand names the row; a group's button says its value and its count. - A resize handle is a focusable
separatornamed after its column, with its width asaria-valuenow. - The filters' bar is a named group; the count of results is announced politely when it changes; each filter in force is a chip with a remove button.
- While loading the table is
aria-busyand its caption says so; an error is announced (role="alert"). - Windowed rows carry
aria-rowindexand the tablearia-rowcount: a screen reader knows where it is in the whole list; the scrolling body is a focusable region.
Clavier
| Touches | Action |
|---|---|
| Tab | Reaches the toolbar, the sort buttons of the headers, the resize handles, the checkboxes, the expand buttons and the links of the cells. |
| Space | Toggles the focused checkbox, sorts by the focused header, or opens the focused row or group. |
| Arrow keys | On a focused resize handle, narrow or widen the column (Shift for larger steps). |
Bonnes pratiques
- Keep
rowsandcolumnsstable (module scope, state, a query's data,useMemo): a new array at each render recomputes every model. - Give every column a
header(it labels the value in a card and names its filter) and the row's name acard: "title"column. - Put
DataTableFiltersintoolbar; mark the columns it filters withfilter: "text" | "facet" | "range", and give a server table's facets theiroptions. - Server mode: keep the
DataTableQueryfromonStateChangein state (or the address), put it in the query key, and passrowCount—placeholderData: keepPreviousDatakeeps the page while the next one loads. - Say what failed in
errorwith a way to retry; say what to do next inempty(anEmptyStatewith an action). - Align amounts and counts to the end (
align: "end") in tabular numbers; pin the name column of a wide table. - Need something the props do not cover?
useDataTable()gives the TanStack instance — never import@tanstack/*in a product.
Composants liés
Générée depuis le code de @krizaka/ui 3.10.0 : son meta.ts, ses exemples et ses types.Modifier cette documentation sur GitHub