Krizaka UI
Krizaka UI
Composants

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.

WebBêtaAffichage de données

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 virtualized for a long list loaded at once (logs, 50,000 events) that must stay smooth.

Quand ne pas l’utiliser

  • For a few figures side by side: stats.À la place : Stat
  • For a gallery of media where the picture matters more than the columns.À la place : Virtual
  • For a list on a phone app: FlashList, through the native list.À la place : List

Installation

Installer

npm install @krizaka/ui @krizaka/tailwind @krizaka/tokens tailwindcss

Styles

@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.

Jobs
Status
Podcast transcriptWhisper largerunning318 s
Catalogue summariesLlama 3.3done96 s
Product shots, autumnFlux 1.1done42 s
Trailer voice-overXTTS v2failed7 s

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

Videos
CreatorVisibility
Sunset over Montréal #1Elena VargaPublic0
Behind the scenes #2Noah PetitFollowers7,919
Studio tour #3Maya ChenDraft15,838
Live Q&A replay #4Elena VargaFollowers3,757
Sunset over Montréal #5Noah PetitPublic11,676
Behind the scenes #6Maya ChenDraft19,595
Studio tour #7Elena VargaPublic7,514
Live Q&A replay #8Noah PetitFollowers15,433

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

Payouts — Loading
Status

Pages

Client pagination under the table, with the rows per page to choose.

Invoices
INV-2026-0001Atelier Lumen2026-01-01€0.00
INV-2026-0002Northwind Studio2026-02-02€41.11
INV-2026-0003Café Brûlé2026-03-03€82.22
INV-2026-0004Kite & Co2026-04-04€123.33
INV-2026-0005Atelier Lumen2026-05-05€164.44
INV-2026-0006Northwind Studio2026-06-06€205.55
INV-2026-0007Café Brûlé2026-07-07€246.66
INV-2026-0008Kite & Co2026-08-08€287.77
INV-2026-0009Atelier Lumen2026-09-09€328.88
INV-2026-0010Northwind Studio2026-01-10€369.99

Groups

groupBy: a row per group with its count and its total (aggregate), opened to see its rows.

Tips this week
CreatorFromAmount
$20.00
Elena Vargamarco_v$5.00
Elena Vargaana.lu$15.00
$10.00
$20.00

Details

renderDetail: a button per row opens a panel under it.

Verification requests
DetailsCreatorRequestStatus
Elena VargaIdentity checkpending

Passport, expires 2031. Selfie matches.

Noah PetitPayout accountpending
Maya ChenIdentity checkapproved

Pinned and resizable

A wide table: the name pinned at the start, columns resized by drag or by the arrow keys.

Model pricing
ModelProviderInput / 1MOutput / 1MContextLatencyRegion
Llama 3.3 70BSelf-hosted$0.23$0.40128k420 mseu-west
Mistral LargeMistral$2.00$6.00128k380 mseu-west
Qwen 2.5 CoderSelf-hosted$0.12$0.2032k210 mseu-central

Cards on a phone

layout="cards": each row a card, the title on top — what auto does in a narrow container.

Your videos
TitleVisibilityViewsTips
Sunset over MontréalPublic12,840$184.00
Behind the scenes, part 2Followers3,120$62.50

Loading

Skeleton rows while the first page loads, the table busy.

Users — Loading
UserRoleJoined

Empty

No record yet: an empty state with the next step.

Invoices
InvoiceAmount

No invoice yet

Your first invoice appears after your first payout.

Error

The request failed: an alert with a retry, in place of the rows.

Audit log
EventAt

The audit log could not be loaded.

50,000 rows

virtualized: only the rows in view are rendered and measured, the header stays.

Events
At
evt-1view2026-10-01 00:00
evt-2tip2026-10-02 01:00
evt-3follow2026-10-03 02:00
evt-4comment2026-10-04 03:00
evt-5view2026-10-05 04:00
evt-6tip2026-10-06 05:00
evt-7follow2026-10-07 06:00
evt-8comment2026-10-08 07:00
evt-9view2026-10-09 08:00
evt-10tip2026-10-10 09:00
evt-11follow2026-10-11 10:00
evt-12comment2026-10-01 11:00
evt-13view2026-10-02 12:00
evt-14tip2026-10-03 13:00
evt-15follow2026-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.

PropTypeDéfautDescription
captionrequisstring—What the table lists ("Jobs", "Your videos"): its <caption>, hidden unless showCaption.
columnsrequisreadonly 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.
rowsrequisreadonly T[]—The rows (stable: module scope, state or a query's data). Sorted, filtered and paginated here unless manual.
classNamestring—Classes merged on the root.
columnVisibilityReadonly<Record<string, boolean>>—Visible (true) or hidden (false) columns, by id (controlled); a column not listed is visible.
defaultColumnVisibilityReadonly<Record<string, boolean>>{}The visibility at first, uncontrolled.
defaultExpandedReadonly<Record<string, boolean>>{}The opened rows at first, uncontrolled.
defaultFiltersReadonly<Record<string, FilterValue>>{}The filters at first, uncontrolled.
defaultGroupByreadonly string[][]The grouping at first, uncontrolled.
defaultPagenumber1The page at first, uncontrolled. Default 1.
defaultPageSizenumber—Rows per page at first, uncontrolled: turns on the pagination under the table.
defaultSelectedreadonly string[][]The ids selected at first, uncontrolled.
defaultSortDataTableSortnullThe sort at first, uncontrolled.
density"comfortable" | "compact"—comfortable (default) · compact.
emptyReactNode—Shown when there is no row (an EmptyState). Default: labels.empty.
errorReactNode—A failure, shown in place of the rows (an Alert, a retry button); announced.
expandedReadonly<Record<string, boolean>>—The opened rows and groups (controlled), by id.
filtersReadonly<Record<string, FilterValue>>—The filters in force (controlled), by column id — what DataTableFilters edits.
footerReactNode—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.
groupByreadonly string[]—The columns the rows are grouped by, in order (controlled) — each groupable.
labelsPartial<DataTableLabels>—The words, passed translated; English by default.
layout"table" | "auto" | "cards"autoauto (default): a table, cards below 36rem of container width. table or cards to force one.
loadingboolean—Rows are loading: skeleton rows (loadingRows of them) and aria-busy.
loadingRowsnumber5How 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.
pagenumber—The page, from 1 (controlled).
pageSizenumber—Rows per page (controlled): turns on the pagination under the table.
pageSizeOptionsreadonly 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.
resizingboolean—Columns can be resized (pointer drag, or the arrow keys on the focused handle).
rowCountnumber—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.
selectableboolean—Adds a checkbox per row and one in the header.
selectedreadonly string[]—The ids selected (controlled).
showCaptionboolean—Shows the caption above the table.
sortDataTableSort—The sorted column (controlled), null for none.
toolbarReactNode—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.

PropTypeDéfautDescription
classNamestring—Classes merged on the button.
defaultOpenboolean—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.

PropTypeDéfautDescription
classNamestring—Classes merged on the button.
filenamestringexport.csvThe 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.

PropTypeDéfautDescription
childrenReactNode—More controls in the bar (a date range picker, a toggle).
classNamestring—Classes merged on the bar.
debouncenumber250The search's wait after the last key, in ms, before the filter applies (a request, in server mode). Default 250.

Accessibilité

  • A table with a caption (hidden unless showCaption) and scope="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 with aria-pressed.
  • An expand button (a row's details, a group, a tree) says its state with aria-expanded and names the row; a group's button says its value and its count.
  • A resize handle is a focusable separator named after its column, with its width as aria-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-busy and its caption says so; an error is announced (role="alert").
  • Windowed rows carry aria-rowindex and the table aria-rowcount: a screen reader knows where it is in the whole list; the scrolling body is a focusable region.

Clavier

TouchesAction
TabReaches the toolbar, the sort buttons of the headers, the resize handles, the checkboxes, the expand buttons and the links of the cells.
SpaceToggles the focused checkbox, sorts by the focused header, or opens the focused row or group.
Arrow keysOn a focused resize handle, narrow or widen the column (Shift for larger steps).

Bonnes pratiques

  • Keep rows and columns stable (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 a card: "title" column.
  • Put DataTableFilters in toolbar; mark the columns it filters with filter: "text" | "facet" | "range", and give a server table's facets their options.
  • Server mode: keep the DataTableQuery from onStateChange in state (or the address), put it in the query key, and pass rowCount — placeholderData: keepPreviousData keeps the page while the next one loads.
  • Say what failed in error with a way to retry; say what to do next in empty (an EmptyState with 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.

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

Sur cette page