Data Grid
DataGrid is an interactive data grid: sorting, row selection, resizable columns and virtualization for tens of thousands of rows.
Import
Install from npm, copy the source into your project with @yugen-ui/cli or the shadcn CLI, or import directly when working in this repo.
// npm install yugen-uiimport { DataGrid, DataGridColumnPicker, createColumn, sortItems, getVisibleColumns, getOrderedColumns, useDataGridSort, useDataGridSelection, useDataGridColumnVisibility, type DataGridColumn, type DataGridSortState,} from "yugen-ui";Variants
Each tile is a standalone example: hover to copy, or press Code to see the source.
Sorting (controlled)
Only columns with compare can be sorted. Sorting is stable, aria-sort is set on the header, and the icon comes with text for screen readers.
Multiple selection + toolbar
selectedItems / onSelectionChange is a Set<string> of ids. The header checkbox has an indeterminate state.
Choosing and ordering columns
DataGridColumnPicker and DataGrid share hiddenColumns and columnOrder, both saved to localStorage. The menu stays open while you check columns; the hideable: false column (Customer) and the last visible column can't be unchecked. Drag an item in the menu (or press Alt + ↑ ↓) to change the column order; the grid follows right away. Hiding the sorted column keeps the row order, and the menu still shows the arrow on that column.
Column resizing, pinned first column
The handle receives focus when you Tab from a header cell: ← / → ±10px, Shift ±50px, double-click to fit the content. The sticky column stays in place when scrolling horizontally.
Virtualizing 10,000 rows
virtualized + height: only rows in the viewport (+ overscan) are rendered. Ctrl + End scrolls to the last row and focuses it.
Loading · empty · error
loading shows a skeleton after 300ms (kept for at least 500ms); no rows shows Empty; errors are handled by AsyncContent, with a Retry button.
RTL
Columns run right to left, ArrowLeft moves toward the end of the row, and dragging the handle left widens the column. No extra prop: the direction comes from DirectionProvider / YugenProvider.
Usage
- Set aria-label (or aria-labelledby) on the grid, and make getRowId return a stable id, not the array index.
- Turn on virtualized for lists longer than a few hundred rows, with a fixed height and a uniform rowHeight.
- Use align="end" for number and currency columns and a fixed width for narrow columns; let the main column flex.
- Don't put several buttons in each cell, since each one is a tab stop. Group them in an actions Menu.
- Don't use DataGrid for layout or card lists. Use Table (static) or List.
- Don't show your own spinner when reloading. Use the loading prop so the loader doesn't flash for less than 300ms.
Props
| Prop · Type | Default | Description |
|---|---|---|
| itemsreadonly T[] | Default: — | Data in its original order; T is inferred for columns and getRowId. |
| columnsDataGridColumn<T>[] | Default: — | Created with createColumn<T>({ columnId, label?, renderHeaderCell?, renderCell, compare?, width?, minWidth?, maxWidth?, resizable?, align?, sticky?, hideable? }). label is the plain-text header and the name in DataGridColumnPicker; use renderHeaderCell when the header needs more than text. |
| columnOrderstring[] | Default: — | Display order of the columns (column ids). Columns missing from the array come after, in columns order; unknown ids are ignored. |
| hiddenColumnsReadonlySet<string> | Default: — | Ids of the columns not shown. A hideable: false column is always shown; hiding every column keeps the first one. A hidden column keeps its resized width and still sorts the rows if it is the sort column; focus moves to the next column when the focused one is hidden. |
| getRowId(item: T) => string | Default: — | Stable row id (key, selection). |
| size"small" | "medium" | "large" | Default: "medium" | Row height 34 / 44 / 56px. |
| sortableboolean | Default: false | Click / Enter / Space on the header of a column with compare sorts by it (the same column again reverses the direction). |
| sortState / defaultSortState{ sortColumn: string | undefined; sortDirection: "ascending" | "descending" } | Default: — | Sort state (controlled / initial). |
| onSortChange(event, state) => void | Default: — | Called when the sort column or direction changes. |
| selectionMode"single" | "multiselect" | Default: — | Adds a selection column. Without it, rows can't be selected. |
| selectedItems / defaultSelectedItemsReadonlySet<string> / Iterable<string> | Default: — | Selected ids (controlled / initial). |
| onSelectionChange(event, { selectedItems: Set<string> }) => void | Default: — | Called when the selection changes (click, Space, Shift + arrow, select all, Ctrl + A). |
| selectionAppearance"brand" | "neutral" | "none" | Default: "brand" | Background of selected rows. |
| subtleSelectionboolean | Default: false | Checkboxes only show on hover / focus or when the row is selected. |
| resizableColumnsboolean | Default: false | Resize handle on the trailing edge of each header (mouse, touch, keyboard; double-click fits the content). |
| columnSizingOptionsRecord<columnId, { defaultWidth?, minWidth?, maxWidth? }> | Default: — | Per-column size overrides (as in Fluent). |
| onColumnResize(event, { columnId, width }) => void | Default: — | Called when a column width changes. Nothing is saved by default. |
| virtualizedboolean | Default: false | Renders only the rows in the viewport; aria-rowcount / aria-rowindex still reflect the real total. |
| rowHeightnumber | Default: — | Row height (px); defaults to the value for size. |
| heightnumber | string | Default: — | Height of the scroll container. virtualized without it → 400px (h-100). |
| overscannumber | Default: 6 | Extra rows rendered above / below the viewport. |
| focusMode"cell" | "row" | Default: "cell" | Arrow keys move by cell or by row. |
| loadingboolean | Default: false | Skeleton after 300ms, kept for at least 500ms (useSpinDelay); aria-busy on the grid. |
| emptyReactNode | Default: — | Content when there are no rows; defaults to an Empty reading “No data”. |
| DataGridColumnPicker | Default: — | The same column array passed to DataGrid; for Table, an array of { columnId, label }. The menu shows label. |
| DataGridColumnPicker | Default: — | Ids of the hidden columns, the same value passed to DataGrid. |
| DataGridColumnPicker | Default: — | Called when a column is checked / unchecked or Show all columns is chosen. Ids of columns that are not in columns right now are kept, so they can be saved. |
| DataGridColumnPicker | Default: — | Column order, the same value passed to DataGrid. |
| DataGridColumnPicker | Default: — | When set, menu items can be dragged (or moved with Alt + ↑ ↓) to change the column order. Also called during the drag so the grid follows. |
| DataGridColumnPicker | Default: — | The sorted column shows an arrow in the menu, even while hidden. |
| DataGridColumnPicker | Default: "end" | Menu alignment relative to the button. Inside a Toolbar, the button takes the Toolbar size and the subtle appearance. |
| useDataGridSort / useDataGridSelection / useDataGridColumnVisibility / sortItems / getVisibleColumns / getOrderedColumnshook / hook / hook / (items, columns, sortState) => T[] / (columns, hiddenColumns) => columns / (columns, columnOrder) => columns | Default: — | Logic you can use on its own, for example for server-side sorting, to drive the selection from a toolbar, or to build your own column picker. |
Messages
Text built into the component: labels, aria-labels, screen reader announcements.
English by default. Change it with the messages prop of YugenProvider, by the namespace below; messagesVi has the Vietnamese set. See Interface language.
messages.dataGrid
| Key · Type | Default (English) | messagesVi |
|---|---|---|
| selectRowstring Tên ô chọn của từng hàng (và tiêu đề cột chọn khi chọn một hàng). | Default (English)"Select row" | messagesVi"Chọn hàng" |
| selectAllstring Tên ô chọn tất cả ở hàng tiêu đề. | Default (English)"Select all rows" | messagesVi"Chọn tất cả hàng" |
| sortAscendingstring Chữ ẩn sau tên cột đang sắp xếp. | Default (English)"(ascending)" | messagesVi"(tăng dần)" |
| sortDescendingstring | Default (English)"(descending)" | messagesVi"(giảm dần)" |
| resizeColumnstring Tên tay nắm đổi độ rộng cột (ghép trước tên cột). | Default (English)"Resize column" | messagesVi"Đổi độ rộng cột" |
| emptyTitlestring | Default (English)"No data" | messagesVi"Không có dữ liệu" |
| emptyDescriptionstring | Default (English)"There are no rows to display." | messagesVi"Chưa có hàng nào để hiển thị." |
| columnsstring Nhãn nút của DataGridColumnPicker. | Default (English)"Columns" | messagesVi"Cột" |
| showAllColumnsstring Mục cuối menu của DataGridColumnPicker. | Default (English)"Show all columns" | messagesVi"Hiện tất cả cột" |
| hiddenColumns(count: number) => string Mô tả nút DataGridColumnPicker khi có cột bị ẩn. | Default (English)(count) => (count === 1 ? "1 column hidden" : `${count} columns hidden`) | messagesVi(count) => `${count} cột đang ẩn` |