A feature-rich, composable datagrid component built with TanStack Table v8, shadcn/ui, and Tailwind CSS v4. This implementation follows the comprehensive Product Requirements Document (PRD) for creating a high-performance, accessible, and customizable data grid.
View the live demo here: https://datagrid-shadcn.netlify.app/
Install the DataGrid component directly into your project:
npx shadcn@latest add https://datagrid-shadcn.netlify.app/r/data-grid.jsonyarn:
yarn dlx shadcn@latest add https://datagrid-shadcn.netlify.app/r/data-grid.jsonpnpm:
pnpm dlx shadcn@latest add https://datagrid-shadcn.netlify.app/r/data-grid.jsonbun:
bunx shadcn@latest add https://datagrid-shadcn.netlify.app/r/data-grid.jsonIf you prefer to install manually:
- Install dependencies:
npm install @tanstack/react-table @tanstack/react-virtual lucide-react class-variance-authority clsx tailwind-merge- Install required shadcn components:
npx shadcn@latest add button checkbox input select context-menu dropdown-menu- Copy the data-grid component files from this repository to your project.
The DataGrid component is deployed and available at:
https://datagrid-shadcn.netlify.app/r/data-grid.json
npm run registry:buildThis generates registry files in public/r/ that can be consumed by the shadcn CLI.
- Build the registry:
npm run registry:build-
Deploy your project to Vercel, Netlify, or any static hosting service
-
Your registry will be available at:
https://your-domain.com/r/data-grid.json
To test the registry locally:
- Start the development server:
npm run dev- In another project, install from local server:
npx shadcn@latest add http://localhost:5173/r/data-grid.json- β Advanced Sorting - Single and multi-column sorting with visual indicators
- β Smart Filtering - Global search and column-specific filtering
- β Row Selection - Multi-row selection with checkboxes and indeterminate states
- β Contextual Actions - Dynamic action dock for bulk operations on selected rows
- β Pagination - Client-side and server-side pagination support
- β Column Management - Show/hide columns, column resizing (enabled by default and configurable)
- β Row Virtualization - Performance optimization for large datasets
- β Inline Cell Editing - Edit data directly within cells, with support for various input types, validation, and configurable behaviors
- β Customizable Context Menus - Add right-click menus to cells and headers, using pre-built utilities or custom items for tailored actions
- β Accessibility - WCAG compliant with full keyboard navigation
- π¨ Composable Design - Built with shadcn/ui principles for maximum customization
- β‘ High Performance - Optimized rendering with TanStack Table and Virtual
- π― TypeScript Support - Fully typed with comprehensive interfaces
- π± Responsive Design - Mobile-friendly with Tailwind CSS v4
- βΏ Accessibility First - ARIA attributes and keyboard navigation
- TanStack Table v8 - Headless table logic and state management
- shadcn/ui - Composable UI components
- Tailwind CSS v4 - Utility-first styling with modern CSS features
- TanStack Virtual - Row virtualization for large datasets
- React 19 - Latest React features
- TypeScript - Type safety and developer experience
- Vite - Fast build tool and development server
# Clone the repository
git clone <repository-url>
cd datagrid-shadcn
# Install dependencies
npm install
# Start development server
npm run devimport { DataGrid, DataGridColumn, DataGridAction } from './components/data-grid'
interface User {
id: string
firstName: string
lastName: string
email: string
role: string
status: 'active' | 'inactive' | 'pending'
}
const columns: DataGridColumn<User>[] = [
{
id: 'firstName',
header: 'First Name',
accessorKey: 'firstName',
enableSorting: true,
enableFiltering: true,
},
{
id: 'email',
header: 'Email',
accessorKey: 'email',
enableSorting: true,
enableFiltering: true,
},
// ... more columns
]
const actions: DataGridAction<User>[] = [
{
id: 'delete',
label: 'Delete Selected',
icon: <Trash2 className="h-4 w-4" />,
variant: 'destructive',
onClick: (selectedRows) => {
// Handle delete action
},
isEnabled: (selectedRows) => selectedRows.length > 0,
},
// ... more actions
]
function App() {
return (
<DataGrid
data={users}
columns={columns}
actions={actions}
enableRowSelection={true}
enableSorting={true}
enableGlobalFilter={true}
enablePagination={true}
pageSize={10}
/>
)
}const columns: DataGridColumn<User>[] = [
{
id: 'status',
header: 'Status',
accessorKey: 'status',
cell: ({ row }) => {
const status = row.original.status
const statusColors = {
active: 'bg-green-100 text-green-800',
inactive: 'bg-red-100 text-red-800',
pending: 'bg-yellow-100 text-yellow-800',
}
return (
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${statusColors[status]}`}>
{status}
</span>
)
},
},
]<DataGrid
data={users}
columns={columns}
manualPagination={true}
manualSorting={true}
manualFiltering={true}
pageCount={totalPages}
onPaginationChange={(pageIndex, pageSize) => {
// Fetch data for new page
}}
onSortingChange={(sorting) => {
// Apply sorting on server
}}
onGlobalFilterChange={(filter) => {
// Apply global filter on server
}}
/><DataGrid
data={largeDataset}
columns={columns}
enableVirtualization={true}
estimateSize={50} // Estimated row height in pixels
/>| Prop | Type | Default | Description |
|---|---|---|---|
data |
TData[] |
- | Array of data objects to display |
columns |
DataGridColumn<TData>[] |
- | Column definitions |
actions |
DataGridAction<TData>[] |
[] |
Bulk actions for selected rows |
enableRowSelection |
boolean |
false |
Enable row selection with checkboxes |
enableMultiRowSelection |
boolean |
true |
Allow multiple row selection |
onRowSelectionChange |
(selectedRows: Row<TData>[]) => void |
undefined |
Callback when row selection changes. |
enableSorting |
boolean |
true |
Enable column sorting |
enableMultiSort |
boolean |
false |
Allow sorting by multiple columns |
manualSorting |
boolean |
false |
Set to true if sorting is handled externally (server-side). Requires onSortingChange or onDataChange. |
onSortingChange |
(sorting: any[]) => void |
undefined |
Callback when sorting state changes. Used with manualSorting. |
enableGlobalFilter |
boolean |
true |
Enable global search filter |
enableColumnFilters |
boolean |
true |
Enable per-column filtering |
manualFiltering |
boolean |
false |
Set to true if filtering (global and column) is handled externally (server-side). Requires onGlobalFilterChange, onColumnFiltersChange, or onDataChange. |
onGlobalFilterChange |
(globalFilter: string) => void |
undefined |
Callback when global filter value changes. Used with manualFiltering. |
onColumnFiltersChange |
(columnFilters: any[]) => void |
undefined |
Callback when column filters change. Used with manualFiltering. |
onDataChange |
(params: DataChangeParams) => void |
undefined |
Unified callback for server-side operations when pagination, sorting, or filtering changes. See DataChangeParams interface. |
enablePagination |
boolean |
true |
Enable pagination |
pageSize |
number |
10 |
Number of rows per page |
pageSizeOptions |
number[] |
[10, 20, 50, 100] |
Available page size options |
manualPagination |
boolean |
false |
Set to true if pagination is handled externally (server-side). Requires pageCount and onPaginationChange or onDataChange. |
pageCount |
number |
undefined |
Total number of pages, required for manualPagination. |
totalCount |
number |
undefined |
Total number of records in the dataset, useful for display with server-side pagination. |
onPaginationChange |
(pageIndex: number, pageSize: number) => void |
undefined |
Callback when pagination state (page index or size) changes. Used with manualPagination. |
enableVirtualization |
boolean |
false |
Enable row virtualization for large datasets |
estimateSize |
number |
35 |
Estimated row height for virtualization |
isLoading |
boolean |
false |
Show loading state |
error |
string | null |
null |
Error message to display |
enableCellEditing |
boolean |
false |
Master switch to enable/disable cell editing. |
defaultEditMode |
CellEditMode |
'click' |
Default mode for triggering cell editing (e.g., 'click', 'doubleClick'). |
onCellEdit |
(value: any, row: Row<TData>, column: Column<TData>) => Promise<boolean> | boolean |
undefined |
Callback when a cell's value is successfully edited and saved. Return false to indicate save failure. |
onCellEditError |
(error: string, row: Row<TData>, column: Column<TData>) => void |
undefined |
Callback when an error occurs during cell editing. |
enableCellContextMenu |
boolean |
false |
Enable right-click context menus on data cells. |
enableHeaderContextMenu |
boolean |
false |
Enable right-click context menus on column headers. |
cellContextMenuItems |
CellContextMenuItem<TData>[] |
[] |
Array of items for cell context menus. |
headerContextMenuItems |
HeaderContextMenuItem<TData>[] |
[] |
Array of items for header context menus. |
enableColumnResizing |
boolean |
true |
Enable/disable column resizing for the entire grid. |
onColumnSizingChange |
(columnSizing: Record<string, number>) => void |
undefined |
Callback when column sizes change due to resizing. |
className |
string |
undefined |
Custom CSS class name for the main DataGrid container. |
'aria-label' |
string |
'Data grid' |
ARIA label for the DataGrid region. |
'aria-describedby' |
string |
undefined |
ARIA describedby attribute for the DataGrid region. |
interface DataGridColumn<TData> {
id: string
header: string | ReactNode
accessorKey?: keyof TData
cell?: ({ row }: { row: Row<TData> }) => ReactNode
enableSorting?: boolean
enableFiltering?: boolean
enableEditing?: boolean | CellEditConfig<TData> // Enable or configure cell editing for this column. See `CellEditConfig<TData>` for advanced options.
enableHiding?: boolean
enableResizing?: boolean
size?: number
minSize?: number
maxSize?: number
}interface DataGridAction<TData> {
id: string
label: string
icon?: ReactNode
variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link'
onClick: (selectedRows: Row<TData>[]) => void | Promise<void>
isEnabled?: (selectedRows: Row<TData>[]) => boolean
isVisible?: (selectedRows: Row<TData>[]) => boolean
}This interface defines the object passed to the onDataChange callback, used for server-side data operations.
| Property | Type | Description |
|---|---|---|
pagination |
{ pageIndex: number; pageSize: number } |
Contains the current page index and page size. |
sorting |
Array<{ id: string; desc: boolean }> |
Array of sorting objects, each with a column id and desc (descending) flag. |
filters |
Array<{ id:string; value: any }> |
Array of column filter objects, each with a column id and filter value. |
globalFilter |
string |
The current global filter string. |
Used to configure cell editing behavior for a column via the DataGridColumn.enableEditing prop.
| Property | Type | Description |
|---|---|---|
enabled |
boolean |
Whether editing is enabled for this column. |
behavior |
CellEditBehavior |
Defines how editing is triggered and saved (e.g., 'click', 'doubleClick', save on blur/enter). See EditBehaviors in types.ts. |
component |
ComponentType<CellEditComponentProps<TData, TValue>> |
Custom React component for the cell editor. Defaults to a text input. See edit-components.tsx for more. |
validate |
(value: TValue, row: Row<TData>) => string | null |
Function to validate the edited value. Return an error string or null. |
onSave |
(value: TValue, row: Row<TData>, column: Column<TData>) => Promise<boolean> | boolean |
Callback when an edit is saved for this specific column. Overrides global onCellEdit for this column if provided. Return false for failure. |
placeholder |
string |
Placeholder text for the edit input. |
disabled |
(row: Row<TData>) => boolean |
Function to conditionally disable editing for specific rows. |
Note: For more advanced CellEditConfig options like onCancel, onEditStart, onEditEnd, and EditBehaviors, please refer to src/components/data-grid/types.ts and the cell editing implementation.
Defines an item for a cell context menu, used with the cellContextMenuItems prop.
| Property | Type | Description |
|---|---|---|
id |
string |
Unique ID for the menu item. |
label |
string |
Text displayed for the menu item. |
icon |
ReactNode |
Optional icon to display next to the label. |
onClick |
(row: Row<TData>, column: Column<TData>, value: any) => void | Promise<void> |
Function called when the item is clicked. |
isEnabled |
(row: Row<TData>, column: Column<TData>, value: any) => boolean |
Optional function to determine if the item is enabled. |
isVisible |
(row: Row<TData>, column: Column<TData>, value: any) => boolean |
Optional function to determine if the item is visible. |
separator |
boolean |
If true, renders a separator instead of a clickable item. |
variant |
'default' | 'destructive' |
Optional variant, e.g., for destructive actions. |
Tip: Utility functions in src/components/data-grid/context-menu-utils.tsx (like copyCellItem()) provide pre-configured CellContextMenuItem objects.
Defines an item for a header context menu, used with the headerContextMenuItems prop.
| Property | Type | Description |
|---|---|---|
id |
string |
Unique ID for the menu item. |
label |
string |
Text displayed for the menu item. |
icon |
ReactNode |
Optional icon to display next to the label. |
onClick |
(column: Column<TData>) => void | Promise<void> |
Function called when the item is clicked. |
isEnabled |
(column: Column<TData>) => boolean |
Optional function to determine if the item is enabled. |
isVisible |
(column: Column<TData>) => boolean |
Optional function to determine if the item is visible. |
separator |
boolean |
If true, renders a separator instead of a clickable item. |
variant |
'default' | 'destructive' |
Optional variant, e.g., for destructive actions. |
Tip: Utility functions in src/components/data-grid/context-menu-utils.tsx (like sortAscendingItem()) provide pre-configured HeaderContextMenuItem objects.
The DataGrid component follows a composable architecture with clear separation of concerns:
- DataGrid - Main container component that orchestrates all functionality
- DataGridHeader - Handles column headers, sorting indicators, and resizing
- DataGridBody - Renders table rows with support for virtualization
- DataGridPagination - Pagination controls and page size selection
- DataGridFilters - Global search and column visibility controls
- DataGridActionDock - Contextual actions for selected rows
The component uses TanStack Table's built-in state management for:
- Row selection state
- Sorting state
- Filtering state
- Pagination state
- Column visibility and sizing
- Memoization - React.memo and useMemo for preventing unnecessary re-renders
- Virtualization - TanStack Virtual for handling large datasets
- Debounced Filtering - Optimized search input handling
- Efficient Updates - Minimal DOM manipulations through TanStack Table
The component is fully customizable using Tailwind CSS classes. All styling is applied through utility classes, making it easy to modify the appearance:
<DataGrid
className="custom-datagrid"
// ... other props
/>The component supports both light and dark themes through Tailwind CSS variables defined in src/index.css.
You can replace any sub-component by creating your own implementation:
// Custom header component
const CustomHeader = () => {
const { table } = useDataGrid()
// Custom implementation
}
// Use in your DataGrid
<DataGrid
// ... props
components={{
Header: CustomHeader
}}
/>The DataGrid component is built with accessibility in mind:
- ARIA Attributes - Proper roles, labels, and descriptions
- Keyboard Navigation - Full keyboard support for all interactions
- Screen Reader Support - Semantic markup and announcements
- Focus Management - Logical tab order and focus indicators
- High Contrast - Support for high contrast themes
| Key | Action |
|---|---|
Tab / Shift+Tab |
Navigate between interactive elements |
Space |
Toggle row selection |
Enter |
Activate buttons and links |
Arrow Keys |
Navigate within dropdown menus |
Escape |
Close dropdown menus |
The component includes comprehensive testing setup:
# Run tests
npm run test
# Run tests with coverage
npm run test:coverage
# Run tests in watch mode
npm run test:watchsrc/
βββ components/
β βββ data-grid/
β β βββ data-grid.tsx # Main DataGrid component
β β βββ data-grid-header.tsx # Header component
β β βββ data-grid-body.tsx # Body component
β β βββ data-grid-pagination.tsx # Pagination component
β β βββ data-grid-filters.tsx # Filters component
β β βββ data-grid-action-dock.tsx # Action dock component
β β βββ context.tsx # React context
β β βββ types.ts # TypeScript interfaces
β β βββ index.ts # Exports
β βββ ui/ # shadcn/ui components
βββ data/
β βββ sample-data.ts # Sample data for demo
βββ lib/
β βββ utils.ts # Utility functions
βββ App.tsx # Demo application
βββ main.jsx # React entry point
βββ index.css # Global styles
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- TanStack Table - For the excellent headless table library
- shadcn/ui - For the beautiful and composable UI components
- Tailwind CSS - For the utility-first CSS framework
- Lucide React - For the beautiful icons