Skip to content

Tables & lists

Records to scan and act on: grids, tables, logs, grouped and bulk tables, spreadsheets and lists.

Installation & usage

npx @youj/cli add tables-data-grid

Also installs avatar, badge, button, checkbox, collapse, command, content-swap, data-table, dropdown-menu, icon-archive, icon-arrow-down, icon-arrow-up, icon-arrow-up-right, icon-check, icon-check-circle, icon-chevron-left, icon-chevron-right, icon-circle-alert, icon-columns, icon-copy, icon-external-link, icon-filter, icon-inbox, icon-layers, icon-link, icon-minus, icon-more-horizontal, icon-rotate-ccw, icon-rotate-cw, icon-search, icon-search-x, icon-sort, icon-trending-down, icon-trending-up, icon-x, input-group, kbd, pagination, popover, presence-list, segmented-control, skeleton, sliding-indicator, sortable-list, switch, tabs, tooltip, use-announcer, use-now, youj-core, youj-labels, youj-presets, youj-ui-core.

The CLI copies the block to blocks/tables/data-grid.tsx with every component it uses, so it’s yours to edit.

Every prop is optional and falls back to the demo, so it renders as you see it until you pass your own. Give it your data, rewrite or translate its strings through copy, and connect its actions:

dataGrid-view.tsx
'use client'

import { DataGrid, type DataGridProps } from '@/blocks/tables/data-grid'

export function DataGridView({ rowActions, density }: Pick<DataGridProps, 'rowActions' | 'density'>) {
  return (
    <DataGrid
      rowActions={rowActions}
      density={density}
      onSortingChange={() => {
        // your code
      }}
      onColumnFiltersChange={() => {
        // your code
      }}
      onGlobalFilterChange={() => {
        // your code
      }}
    />
  )
}

Reference

Props65 props, 11 types

Props

DataGrid props
PropType
rowsReadonlyArray<TData>

The records, one per row. Default: 18 projects, with the demo's columns.

–
columnsReadonlyArray<DataGridColumnDef<TData>>

The columns, from createDataGridColumns (this block's data-grid/columns). Default: the projects' Project, Status, Lead, Progress, Velocity, Budget, Target and Tags.

–
getRowId(row: TData, index: number) => string

Stable row identity: selection, expansion and row motion key on it. Default: its id field.

–
getRowLabel(row: TData) => string

Names a row for its checkbox, its menus and announcements. Default: its first column's value.

–
titleReactNode

The heading over the grid. Without one the grid stands alone, named by label. Default: "Projects" with the demo's rows.

–
descriptionReactNode

A line under the title.

–
headingLevel2 | 3 | 4

The title's level when embedded.

default 2

2
labelstring

The grid's accessible name, when it has no title.

–
aria-labelledbystring

Identifies the element (or elements) that labels the current element.

–
initialStatePartial<DataGridState>

Starting state for every slice you don't control.

–
statePartial<DataGridState>

Controlled slices. Pair each with its change callback.

–
onSortingChangeChange<SortingState>–
onColumnFiltersChangeChange<ColumnFiltersState>–
onGlobalFilterChangeChange<string>–
onRowSelectionChangeChange<RowSelectionState>–
onColumnVisibilityChangeChange<ColumnVisibilityState>–
onColumnOrderChangeChange<ColumnOrderState>–
onColumnSizingChangeChange<ColumnSizingState>–
onGroupingChangeChange<string | null>–
onExpandedChangeChange<Record<string, boolean>>–
onPageIndexChangeChange<number>–
selectableboolean | ((row: TData) => boolean)

A checkbox on every row, or a function deciding per row.

–
bulkActions(selection: DataGridSelection<TData>) => ReactNode

The selection bar's actions.

–
rowActions(row: TData) => DataGridRowAction<TData>[]

Each row's actions: inline ones show as icon buttons on hover, and all are in its "…" menu.

–
onRowOpen(row: TData) => void

Enter on a row (or a click on its name) opens it.

–
onRowClick(row: TData) => void

A click on a row (or card), anywhere but its own controls (checkbox, actions, links, detail panel) and not while text is selected. Keyboard opens go through onRowOpen.

–
renderDetail(row: TData) => ReactNode

A detail panel under a row, opened from its chevron (or Enter, when rows don't open).

–
activeRowIdstring | null

The row open elsewhere (in a side panel or a drawer): it keeps the selection's tint and bar, and aria-current. Default: none.

–
densityDataGridDensity
'compact''comfortable''spacious'
–
defaultDensityDataGridDensity
'compact''comfortable''spacious'

Default: compact inside a compact layout density (YoujProvider's density), else comfortable.

–
onDensityChangeChange<DataGridDensity>–
stripedboolean

Alternate rows take a faint tint, instead of hairlines alone.

–
enableMultiSortboolean

Shift+click on a header adds it to the sort.

default true

true
enableColumnResizingboolean

Drag a header's edge to resize its column.

default true

true
pinFirstColumnboolean

Pins the first column (the identity) while the grid scrolls sideways.

default true

true
searchboolean | { placeholder?: string; shortcut?: string | false }

A search over every visible column.

default true

true
filtersboolean

The Filter menu, for columns that filter (status, tags, people…).

default true

true
groupingboolean

The Group menu, for columns that group.

default true

true
columnsMenuboolean

The Columns menu: show, hide and reorder.

default true

true
densitySwitchboolean

The density switch.

default true

true
viewsReadonlyArray<DataGridView>

Saved views, as tabs over the toolbar.

–
activeViewstring–
defaultActiveViewstring–
onActiveViewChangeChange<string>–
pageSizenumber | false

Rows per page, or false for every row. Grouped rows aren't paged: groups fold instead.

default 25

25
virtualizeboolean

Renders only the rows in view, for thousands of rows. Needs a height. Paging is off.

default false

false
heightnumber | string

The scroll area's height: the header sticks and virtualized rows scroll inside it.

–
maxHeightnumber | string–
onEndReached() => void

Called when a virtualized grid scrolls to its end: load the next rows.

–
loadingMoreboolean

Rows are on their way (with onEndReached): skeleton rows follow the last.

–
loadingboolean

No rows yet: skeleton rows that resolve into the data. With rows: a progress line, and nothing moves.

default false

false
skeletonRowsnumber–
errorReactNode

Replaces the rows with an error panel. A string is its message.

–
onRetry() => void–
emptyDataGridEmpty

The empty state: its title, description, an illustration or icon, and an action.

–
layout'auto' | 'table' | 'cards'

auto lays rows out as cards when the grid is narrower than cardsBelow. Default auto.

–
cardsBelownumber

The width (px) under which auto shows cards.

default 640

640
toolbarReactNode

The start of the toolbar: a scope switch, a count.

–
actionsReactNode

The end of the toolbar: "New customer".

–
nownumber | Date

The moment relative dates count from. Default: the browser's clock, each minute.

–
themeThemeInput

Wraps the grid in this theme. Default: the theme around it.

–
embeddedboolean

Inside another page: the grid alone, at its natural width (72rem at 100% scale) unless fill.

default true

true
fillboolean

Take the whole width it's given instead of stopping at its natural width.

default false

false
copyPartial<DataGridCopy>

Any of the grid's strings, to rewrite or translate. noun names the rows ("customers"). A locale pack on YoujProvider translates the rest.

–
localestring

Formats its numbers and dates (BCP 47). Default: the app's locale (YoujProvider).

–

DataGridState

DataGridState props
PropType
sortingRequiredSortingState–
columnFiltersRequiredColumnFiltersState–
globalFilterRequiredstring–
rowSelectionRequiredRowSelectionState–
columnVisibilityRequiredColumnVisibilityState–
columnOrderRequiredColumnOrderState–
columnSizingRequiredColumnSizingState–
columnPinningRequiredColumnPinningState–
groupingRequiredstring | null

The column rows are grouped by, or null.

–
expandedRequiredRecord<string, boolean>

Rows whose detail panel is open.

–
collapsedGroupsRequiredRecord<string, boolean>

Groups that are folded shut, by key.

–
pageIndexRequirednumber–
pageSizeRequirednumber–

DataGridView

DataGridView props
PropType
idRequiredstring–
labelRequiredstring–
stateRequiredPartial<Pick<DataGridState, 'sorting' | 'columnFilters' | 'globalFilter' | 'grouping' | 'columnVisibility'>>

What the view shows. Slices it leaves out go back to their defaults.

–
countnumber

Shown after its name, e.g. how many rows it holds.

–

DataGridSelection

DataGridSelection props
PropType
countRequirednumber

How many rows are selected.

–
rowsRequiredTData[]

The selected rows, in view order.

–
idsRequiredstring[]–
clearRequired() => void–

Project

Project props
PropType
idRequiredstring–
nameRequiredstring–
teamRequired'Platform' | 'Growth' | 'Mobile' | 'Design systems' | 'Security'–
statusRequiredProjectStatus
'on-track''at-risk''off-track''paused''done'
–
membersRequiredDataGridPerson[]–
doneRequirednumber–
tasksRequirednumber–
velocityRequirednumber[]

Story points shipped each week, the last ten weeks.

–
budgetRequirednumber–
targetRequiredstring–
tagsRequiredstring[]–
summaryRequiredstring–

DataGridColumnOptions

DataGridColumnOptions props
PropType
idstring

Required when the accessor is a function.

–
headerstring | (() => ReactNode)–
sizenumber–
minSizenumber–
maxSizenumber–
enableSortingboolean–
enableHidingboolean–
enableResizingboolean–
render(value: TValue, row: TData) => ReactNode

Draws the cell yourself. The type's sorting, search, filter and grouping stay.

–
metaPartial<DataGridColumnMeta>

Anything the type sets, changed.

–

DataGridRowAction

DataGridRowAction props
PropType
labelRequiredstring–
iconComponentType<{ className?: string; size?: number }>–
onSelectRequired(row: TData) => void–
inlineboolean

Also an icon button on the row, shown on hover and focus.

–
destructiveboolean–
separatedboolean

A line above it in the menu.

–
disabledboolean–
shortcutstring

Shown in the menu beside it, e.g. "⌘E".

–

DataGridEmpty

DataGridEmpty props
PropType
titlestring–
descriptionReactNode–
illustrationReactNode

An illustration or an icon above the words. Default: an inbox.

–
actionReactNode

The next step: "Add a customer".

–

DataGridColumnMeta

DataGridColumnMeta props
PropType
kindDataGridColumnKind
'identity''status''tags''meter''trend''currency''number''date''people''boolean''id''link''text'

What the column holds; the column types set it.

–
labelstring

The column's name in words: menus, cards, announcements. Default: a string header, then the id.

–
align'start' | 'center' | 'end'

Cell and header alignment. Figures default to end.

–
numericboolean

Figures: tabular mono, end-aligned.

–
growboolean

Takes the width the other columns leave (never narrower than its size).

–
facetDataGridFacet

A filter in the toolbar's Filter menu.

–
groupableboolean

Rows can be grouped by this column.

–
groupKey(value: unknown) => string

The group a cell value falls in. Default: the value as a string.

–
groupLabel(key: string) => ReactNode

A group's heading. Default: the key.

–
groupOrder(a: string, b: string) => number

Orders the groups. Default: the options' order, else A to Z.

–
aggregate'sum' | 'mean' | ((values: unknown[]) => ReactNode)

A figure for each group's footer: a sum, a mean, or your own.

–
formatAggregate(value: number) => ReactNode

Formats a sum or mean aggregate like the column's cells.

–
skeletonDataGridSkeleton
'text''identity''badge''tags''number''meter''trend''people''toggle''none'

The placeholder shape while the grid loads.

–
card'title' | 'badge' | 'field' | 'hidden'

Its place on a card, when the grid lays out as cards: the title, a badge beside it, a field, or left off.

–
searchText(value: unknown) => string

What the search matches for this column. Default: the value as text.

–
cellClassNamestring

Classes for this column's body cells.

–
headerClassNamestring

Classes for this column's header cell.

–

DataGridFacet

DataGridFacet props
PropType
titlestring

The filter's name in the menu and on its chip. Default: the column's label.

–
optionsreadonly DataGridOption[]

The values offered, in order. Default: every value found in the rows.

–
keysRequired(value: unknown) => string[]

The values a cell holds: one for a status, several for tags or people.

–
describe(key: string, value: unknown) => DataGridOption | undefined

Names a value that isn't in options (a person found in the rows).

–

DataGridOption

DataGridOption props
PropType
valueRequiredstring–
labelRequiredstring–
toneDataGridOptionTone
'neutral''accent''success''warning''danger''chart-1''chart-2''chart-3''chart-4''chart-5'
–
srcstring

An image for the option (a person's avatar).

–

DataGridPerson

DataGridPerson props
PropType
idRequiredstring–
nameRequiredstring–
srcstring–
Strings76 keys for copy

Strings

copy takes any of these keys; the rest keep their defaults. A key whose text holds a value (a count, a name) takes a function.

searchjustNowactionsclearFiltersviewscopiedcopyFailedsearchPlaceholderselectRowselectedcolumnsnoItemstryAgainsortByclearSelectionclearSearchclearAllactiveFiltersremoveFilterrowitemCountloadFailedpaginationnoMatchesemptyHintescKeytrendUptrendDowntrendFlatrowNumbertoggleColumnsresetColumnssortAscendingsortDescendingrowActionsrowDensitycompactRowscomfortableRowsfilterByfilterNoOptionsclearFilterpreviousPagenextPagerangeOfpageOfselectionActionsselectAllCountselectAllRowsselectAllOnPagesortedBysortedColumnsortClearedselectionClearedselectedRowsmatchCounttotalCountcellOfgridHintArrowsgridHintSelectgridHintSortnoMatchesHintspaciousRowsgroupBynoGroupinggroupedBygroupingClearedshowDetailshideDetailscopyValueaddFilterbooleanYesbooleanNovalueOfMaxgridHintOpengroupTotalnoun
Built from21 components
Building your ownthe engines, the kit, server data

Building your own

Contacts, Orders and Inventory are the Data table with columns, copy and a header of their own, and Logs is the Data table virtualized beside an inspector: read them to see how a domain table is put together. For a table of your own, start from the Data table and pass rows and columns (made with createDataTableColumnHelper from @youj/react/data-table). For data that lives on a server, pass manual with one page of rows and the rowCount, and own the sorting, filters and pages through state and their on…Change callbacks.

The parts these tables are made of are components: the Table primitives and the Data table kit (column header, toolbar, search, filters and chips, view options, pagination, selection column and bar, saved views, the cell editor, the keyboard grid and CSV export), from @youj/react/data-table. Use them when a table needs a layout the engines don't have.

Accessibilitygrid and treegrid roles, keyboard, announcements, motion

Accessibility

The Data table and the tables built on it are a native <table role="grid"> named by their title, and a single Tab stop: the arrow keys move between cells, Home and End to a row's ends, Ctrl+Home and Ctrl+End to the corners, Page Up and Page Down a screen at a time. Space selects a row (Shift+Space a range), Ctrl+A or ⌘A every row and Escape clears the selection; Enter, F2 or typing edits a cell, Alt+↓ opens a header's menu and Shift+←/→ resizes a column. The keys are listed in the grid's description. aria-rowcount and aria-rowindex keep every row's place when the table is paged or virtualized, headers carry aria-sort, and selection uses aria-multiselectable and aria-selected. Saves, rollbacks, sorting and selection are announced in a live region.

In Orders the command bar is a group named "Selection actions"; each action's key is in its aria-keyshortcuts, and a refund asks first in an alert dialog that names the count. In Inventory the name box is an output named "Cell", the formula bar is named for the cell it edits ("Contents of E7"), a value that doesn't fit is described on the field with aria-invalid, and undo, redo, copy and paste are announced. Low stock is said in words, not only in amber. The stories show other worlds, with their own names and words.

Budget is a treegrid: every row carries aria-level, aria-posinset and aria-setsize, groups aria-expanded. Up and Down move between rows, Right opens a group (or moves into it), Left closes it (or moves to its parent), mirrored in right-to-left text, and Enter opens a line. Variance is read as over or under budget, and opening or closing a group is announced. The stories show other worlds, with their own names and words.

Logs keeps the inspector in a complementary region named "Event". The pill for waiting events is a button, and a polite status says once that events are waiting rather than counting each one. Levels are written out, so color is never the only signal.

Cause log lists the entries in a real table with a tick per row and one for all; every tick is named for its source and the time. Each source's strip is an image whose text gives its entries and total in the last 24 hours, and Assign cause is a menu that stays disabled until a stop is ticked and announces the result.

Preview list is a labelled list of projects; each row names its status in words (the dot only echoes it), its picture is decorative, and the icon buttons carry the project's name in their labels and tooltips. Search and sort are named controls, and the list scrolls inside the card so a search never resizes it.

Transfer list groups the overall meter under a label, and every row is a named progress bar with pause, resume, cancel and retry buttons named for the item. While it loads the skeletons are one named status.

Under reduced motion rows change in place instead of gliding, the waiting-events pill appears without moving, the live dot stops pulsing and groups open at once.