Marble UI
← Components

Data Grid

Preview: Default

Data grid: TanStack Table v9 for search, sorting, pagination and column visibility, rendered with the React Aria Table for grid keyboard navigation and selection.

Import

import { DataGrid, DataGridCheckbox } from '@marbleui/ui'

Props

<DataGrid>

PropTypeDefaultDescription
aria-label *requiredstring—
columns *requiredDataGridColumn<T>[]—
data *requiredT[]—
classNamestring—
density'compact' | 'regular''regular'
emptyTextstring'No results'
onSelectionChange(keys: Selection) => void—
pageSizenumber10
searchablebooleantrue
selectionMode'single' | 'none' | 'multiple''multiple'
stripedboolean—
toolbarReactNode—Extra controls in the toolbar (e.g. a FilterBar, bulk actions).

<DataGridCheckbox>alias of Checkbox

Checkbox with label and description; supports indeterminate, groups and a card style.

PropTypeDefaultDescription
descriptionReactNode—
variant'default' | 'card'—

Also accepts React Aria props (111).

Usage notes

Full data table: search, sorting, pagination, column visibility and row selection (TanStack Table v9 rendered with React Aria Table, so it’s a proper keyboard grid).

const columns: DataGridColumn<Order>[] = [
  { id: 'id', header: 'Order', isRowHeader: true, value: (o) => o.id },
  { id: 'amount', header: 'Amount', align: 'end', sortable: true, value: (o) => o.amount, cell: (o) => money(o.amount) },
]
<DataGrid aria-label="Orders" data={orders} columns={columns} pageSize={10} />
  • value drives sorting and search; cell only renders. Keep them consistent.
  • Mark one column isRowHeader so screen readers announce rows by it.
  • Every row needs a stable id. Use toolbar for filters and bulk actions; selectionMode="none" for read-only tables.
  • For a plain table without search/pagination use Table.
Example source · show
import { Avatar } from '../avatar'
import { Badge } from '../badge'
import { DataGrid, type DataGridColumn } from './data-grid'

type User = { id: number; name: string; email: string; role: 'Owner' | 'Admin' | 'Member' | 'Guest'; status: 'Active' | 'Invited' | 'Suspended'; seats: number; joined: Date }

const first = ['Maya', 'Charles', 'Ada', 'Grace', 'Alan', 'Katherine', 'Linus', 'Margaret', 'Tim', 'Barbara', 'Dennis', 'Radia']
const last = ['Chen', 'Webb', 'Lovelace', 'Hopper', 'Turing', 'Johnson', 'Torvalds', 'Hamilton', 'Berners-Lee', 'Liskov', 'Ritchie', 'Perlman']
const roles = ['Owner', 'Admin', 'Member', 'Member', 'Guest'] as const
const statuses = ['Active', 'Active', 'Active', 'Invited', 'Suspended'] as const
const users: User[] = Array.from({ length: 48 }, (_, i) => {
  const name = `${first[i % 12]} ${last[(i * 5) % 12]}`
  return {
    id: i + 1,
    name,
    email: `${name.split(' ')[0]!.toLowerCase()}.${i}@acme.com`,
    role: roles[i % 5]!,
    status: statuses[(i * 3) % 5]!,
    seats: ((i * 7) % 12) + 1,
    joined: new Date(2025, (i * 3) % 12, ((i * 11) % 27) + 1),
  }
})

const tone = { Active: 'success', Invited: 'accent', Suspended: 'danger' } as const
const dateFmt = new Intl.DateTimeFormat('en', { month: 'short', day: 'numeric', year: 'numeric' })

const columns: DataGridColumn<User>[] = [
  { id: 'name', header: 'Name', isRowHeader: true, sortable: true, hideable: false, value: (u) => u.name, cell: (u) => (
    <span className="flex items-center gap-2.5"><Avatar size="xs" name={u.name} /><span className="flex flex-col"><span className="font-medium">{u.name}</span><span className="text-xs text-fg-muted">{u.email}</span></span></span>
  ) },
  { id: 'role', header: 'Role', sortable: true, value: (u) => u.role },
  { id: 'status', header: 'Status', sortable: true, value: (u) => u.status, cell: (u) => <Badge tone={tone[u.status]} dot>{u.status}</Badge> },
  { id: 'seats', header: 'Seats', sortable: true, align: 'end', value: (u) => u.seats },
  { id: 'joined', header: 'Joined', sortable: true, value: (u) => u.joined, cell: (u) => <span className="text-fg-muted">{dateFmt.format(u.joined)}</span> },
]

export const styles = {
  Default: () => <DataGrid aria-label="Members" data={users} columns={columns} pageSize={8} className="w-[46rem]" />,
  Compact: () => <DataGrid aria-label="Members" data={users} columns={columns} pageSize={12} density="compact" striped selectionMode="none" className="w-[46rem]" />,
}

Source: packages/ui/src/data-grid/data-grid.tsx

Catalog

Category
data-grid
Priority
Pro
Primitive
composed
React Aria
Table + Virtualizer