Dice UI
Components

Data Grid

A high-performance editable data grid component with virtualization, keyboard navigation, and cell editing capabilities.

DocsAPI
"use client";
 
import { faker } from "@faker-js/faker";
import type { ColumnDef } from "@tanstack/react-table";
import * as React from "react";
import { DataGrid } from "@/components/data-grid/data-grid";
import { useDataGrid } from "@/hooks/use-data-grid";
 
interface SkateTrick {
  id: string;
  trickName?: string;
  skaterName?: string;
  difficulty?: "beginner" | "intermediate" | "advanced" | "expert";
  variant?: "flip" | "grind" | "grab" | "transition" | "manual" | "slide";
  landed?: boolean;
  attempts?: number;
  bestScore?: number;
  location?: string;
  dateAttempted?: string;
}
 
const skateSpots = [
  "Venice Beach Skate Park",
  "Burnside Skate Park",
  "Love Park (Philadelphia)",
  "MACBA (Barcelona)",
  "Southbank (London)",
  "FDR Skate Park",
  "Brooklyn Banks",
  "El Toro High School",
  "Hubba Hideout",
  "Wallenberg High School",
  "EMB (Embarcadero)",
  "Pier 7 (San Francisco)",
] as const;
 
const skateTricks = {
  flip: [
    "Kickflip",
    "Heelflip",
    "Tre Flip",
    "Hardflip",
    "Inward Heelflip",
    "Frontside Flip",
    "Backside Flip",
    "Varial Flip",
    "Varial Heelflip",
    "Double Flip",
    "Laser Flip",
    "Anti-Casper Flip",
    "Casper Flip",
    "Impossible",
    "360 Flip",
    "Big Spin",
    "Bigspin Flip",
  ],
  grind: [
    "50-50 Grind",
    "5-0 Grind",
    "Nosegrind",
    "Crooked Grind",
    "Feeble Grind",
    "Smith Grind",
    "Lipslide",
    "Boardslide",
    "Tailslide",
    "Noseslide",
    "Bluntslide",
    "Nollie Backside Lipslide",
    "Switch Frontside Boardslide",
  ],
  grab: [
    "Indy Grab",
    "Melon Grab",
    "Stalefish",
    "Tail Grab",
    "Nose Grab",
    "Method",
    "Mute Grab",
    "Crail Grab",
    "Seatbelt Grab",
    "Roast Beef",
    "Chicken Wing",
    "Tweaked Indy",
    "Japan Air",
  ],
  transition: [
    "Frontside Air",
    "Backside Air",
    "McTwist",
    "540",
    "720",
    "900",
    "Frontside 180",
    "Backside 180",
    "Frontside 360",
    "Backside 360",
    "Alley-Oop",
    "Fakie",
    "Revert",
    "Carve",
    "Pump",
    "Drop In",
  ],
  manual: [
    "Manual",
    "Nose Manual",
    "Casper",
    "Rail Stand",
    "Pogo",
    "Handstand",
    "One Foot Manual",
    "Spacewalk",
    "Truckstand",
    "Primo",
  ],
  slide: [
    "Powerslide",
    "Bert Slide",
    "Coleman Slide",
    "Pendulum Slide",
    "Stand-up Slide",
    "Toeside Slide",
    "Heelside Slide",
  ],
} as const;
 
function generateTrickData(): SkateTrick[] {
  return Array.from({ length: 30 }, () => {
    const variant = faker.helpers.arrayElement(
      Object.keys(skateTricks) as Array<keyof typeof skateTricks>,
    );
    const trickName = faker.helpers.arrayElement(skateTricks[variant]);
    const skaterName = faker.person.fullName();
    const attempts = faker.number.int({ min: 1, max: 50 });
    const landed = faker.datatype.boolean(0.6);
 
    const getDifficulty = (trick: string): SkateTrick["difficulty"] => {
      const expertTricks = [
        "Tre Flip",
        "900",
        "McTwist",
        "Laser Flip",
        "Impossible",
      ];
      const advancedTricks = [
        "Hardflip",
        "720",
        "540",
        "Crooked Grind",
        "Switch Frontside Boardslide",
      ];
      const intermediateTricks = [
        "Kickflip",
        "Heelflip",
        "Frontside 180",
        "50-50 Grind",
        "Boardslide",
      ];
 
      if (expertTricks.some((t) => trick.includes(t))) return "expert";
      if (advancedTricks.some((t) => trick.includes(t))) return "advanced";
      if (intermediateTricks.some((t) => trick.includes(t)))
        return "intermediate";
      return "beginner";
    };
 
    const difficulty = getDifficulty(trickName);
 
    return {
      id: faker.string.nanoid(),
      trickName,
      skaterName,
      difficulty,
      variant,
      landed,
      attempts,
      bestScore: landed
        ? faker.number.int({ min: 6, max: 10 })
        : faker.number.int({ min: 1, max: 5 }),
      location: faker.helpers.arrayElement(skateSpots),
      dateAttempted:
        faker.date
          .between({
            from: new Date(2023, 0, 1),
            to: new Date(),
          })
          .toISOString()
          .split("T")[0] ?? "",
    };
  });
}
 
const initialData: SkateTrick[] = generateTrickData();
 
export function DataGridDemo() {
  const [data, setData] = React.useState<SkateTrick[]>(initialData);
 
  const columns = React.useMemo<ColumnDef<SkateTrick>[]>(
    () => [
      {
        id: "trickName",
        accessorKey: "trickName",
        header: "Trick name",
        meta: {
          cell: {
            variant: "short-text",
          },
        },
        minSize: 180,
      },
      {
        id: "skaterName",
        accessorKey: "skaterName",
        header: "Skater",
        meta: {
          cell: {
            variant: "short-text",
          },
        },
        minSize: 150,
      },
      {
        id: "difficulty",
        accessorKey: "difficulty",
        header: "Difficulty",
        meta: {
          cell: {
            variant: "select",
            options: [
              { label: "Beginner", value: "beginner" },
              { label: "Intermediate", value: "intermediate" },
              { label: "Advanced", value: "advanced" },
              { label: "Expert", value: "expert" },
            ],
          },
        },
        minSize: 120,
      },
      {
        id: "variant",
        accessorKey: "variant",
        header: "Category",
        meta: {
          cell: {
            variant: "select",
            options: [
              { label: "Flip", value: "flip" },
              { label: "Grind", value: "grind" },
              { label: "Grab", value: "grab" },
              { label: "Transition", value: "transition" },
              { label: "Manual", value: "manual" },
              { label: "Slide", value: "slide" },
            ],
          },
        },
        minSize: 120,
      },
      {
        id: "landed",
        accessorKey: "landed",
        header: "Landed",
        meta: {
          cell: {
            variant: "checkbox",
          },
        },
        minSize: 100,
      },
      {
        id: "attempts",
        accessorKey: "attempts",
        header: "Attempts",
        meta: {
          cell: {
            variant: "number",
            min: 1,
            max: 100,
          },
        },
        minSize: 100,
      },
      {
        id: "bestScore",
        accessorKey: "bestScore",
        header: "Score",
        meta: {
          cell: {
            variant: "number",
            min: 1,
            max: 10,
          },
        },
        minSize: 110,
      },
      {
        id: "location",
        accessorKey: "location",
        header: "Location",
        meta: {
          cell: {
            variant: "select",
            options: skateSpots.map((spot) => ({ label: spot, value: spot })),
          },
        },
        minSize: 180,
      },
      {
        id: "dateAttempted",
        accessorKey: "dateAttempted",
        header: "Attempted at",
        meta: {
          cell: {
            variant: "date",
          },
        },
        minSize: 130,
      },
    ],
    [],
  );
 
  const onRowAdd = React.useCallback(() => {
    setData((prev) => [...prev, { id: faker.string.nanoid() }]);
    return { rowIndex: data.length, columnId: "trickName" };
  }, [data.length]);
 
  const { table, ...gridProps } = useDataGrid({
    columns,
    data,
    onDataChange: setData,
  });
 
  return (
    <DataGrid {...gridProps} table={table} height={340} onRowAdd={onRowAdd} />
  );
}

Installation

Install the components and dependencies:

npx [email protected] add "https://diceui.com/r/data-grid"

Features

The Data Grid component provides a comprehensive spreadsheet-like experience with:

  • High Performance: Virtualized rows and columns for handling large datasets
  • Cell Editing: In-place editing with various cell types (text, number, select, date, etc.)
  • Keyboard Navigation: Full keyboard support with Excel-like shortcuts
  • Context Menu: Right-click actions for rows and cells
  • Sorting & Filtering: Built-in sorting and search capabilities
  • Row Management: Add, delete, and reorder rows
  • Column Resizing: Adjustable column widths
  • Selection: Single and multi-cell selection
  • Copy/Paste: Clipboard operations support

Basic Usage

import type { ColumnDef } from "@tanstack/react-table";
import * as React from "react";
import { DataGrid } from "@/components/data-grid/data-grid";
import { useDataGrid } from "@/hooks/use-data-grid";

interface Person {
  id: string;
  name: string;
  email: string;
  age: number;
  status: "active" | "inactive";
}

const data: Person[] = [
  {
    id: "1",
    name: "John Doe",
    email: "[email protected]",
    age: 30,
    status: "active",
  },
  // ... more data
];

export default function DataGridDemo() {
  const [gridData, setGridData] = React.useState<Person[]>(data);

  const columns = React.useMemo<ColumnDef<Person>[]>(() => [
    {
      id: "name",
      accessorKey: "name",
      header: "Name",
      meta: {
        label: "Name",
        cell: {
          variant: "short-text",
        },
      },
    },
    {
      id: "email",
      accessorKey: "email",
      header: "Email",
      meta: {
        label: "Email",
        cell: {
          variant: "short-text",
        },
      },
    },
    {
      id: "age",
      accessorKey: "age",
      header: "Age",
      meta: {
        label: "Age",
        cell: {
          variant: "number",
          min: 0,
          max: 120,
        },
      },
    },
    {
      id: "status",
      accessorKey: "status",
      header: "Status",
      meta: {
        label: "Status",
        cell: {
          variant: "select",
          options: [
            { label: "Active", value: "active" },
            { label: "Inactive", value: "inactive" },
          ],
        },
      },
    },
  ], []);

  const { table, ...gridProps } = useDataGrid({
    data: gridData,
    columns,
    onDataChange: setGridData,
    getRowId: (row) => row.id,
  });

  return (
    <div className="h-[600px] w-full">
      <DataGrid table={table} {...gridProps} />
    </div>
  );
}

Sort Menu

The DataGridSortMenu provides sorting capabilities with drag-and-drop reordering.

Features

  • Multiple column sorting
  • Drag and drop reordering
  • Ascending and descending directions

Installation

npx [email protected] add "https://diceui.com/r/data-grid-sort-menu"

Row Height Menu

The DataGridRowHeightMenu allows users to adjust row heights for better data visibility.

Features

  • Multiple row height options (short, medium, tall, extra-tall)
  • Persistent height settings
  • Smooth transitions

Installation

npx [email protected] add "https://diceui.com/r/data-grid-row-height-menu"

View Menu

The DataGridViewMenu provides column visibility controls.

Features

  • Toggle column visibility
  • Show/hide all columns
  • Column search and filtering

Installation

npx [email protected] add "https://diceui.com/r/data-grid-view-menu"

Cell Types

The Data Grid supports various cell types for different data formats:

Short Text Cell

{
  id: "name",
  accessorKey: "name",
  header: "Name",
  meta: {
    label: "Name",
    cell: {
      variant: "short-text",
    },
  },
}

Long Text Cell

{
  id: "notes",
  accessorKey: "notes",
  header: "Notes",
  meta: {
    label: "Notes",
    cell: {
      variant: "long-text",
    },
  },
}

Number Cell

{
  id: "price",
  accessorKey: "price",
  header: "Price",
  meta: {
    label: "Price",
    cell: {
      variant: "number",
      min: 0,
      step: 0.01,
    },
  },
}

Select Cell

{
  id: "category",
  accessorKey: "category",
  header: "Category",
  meta: {
    label: "Category",
    cell: {
      variant: "select",
      options: [
        { label: "Electronics", value: "electronics" },
        { label: "Clothing", value: "clothing" },
        { label: "Books", value: "books" },
      ],
    },
  },
}

Multi-Select Cell

{
  id: "skills",
  accessorKey: "skills",
  header: "Skills",
  meta: {
    label: "Skills",
    cell: {
      variant: "multi-select",
      options: [
        { label: "JavaScript", value: "javascript" },
        { label: "TypeScript", value: "typescript" },
        { label: "React", value: "react" },
      ],
    },
  },
}

Date Cell

{
  id: "startDate",
  accessorKey: "startDate",
  header: "Start Date",
  meta: {
    label: "Start Date",
    cell: {
      variant: "date",
    },
  },
}

Checkbox Cell

{
  id: "isActive",
  accessorKey: "isActive",
  header: "Active",
  meta: {
    label: "Active",
    cell: {
      variant: "checkbox",
    },
  },
}

Additional Components

The Data Grid comes with additional components for enhanced functionality:

Row Selection

import { Checkbox } from "@/components/ui/checkbox";

// Add to your columns array
{
  id: "select",
  header: ({ table }) => (
    <Checkbox
      checked={
        table.getIsAllPageRowsSelected() ||
        (table.getIsSomePageRowsSelected() && "indeterminate")
      }
      onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
    />
  ),
  cell: ({ row }) => (
    <Checkbox
      checked={row.getIsSelected()}
      onCheckedChange={(value) => row.toggleSelected(!!value)}
    />
  ),
  size: 40,
  enableSorting: false,
  enableHiding: false,
}

Toolbar Components

import { DataGridSortMenu } from "@/components/data-grid/data-grid-sort-menu";
import { DataGridRowHeightMenu } from "@/components/data-grid/data-grid-row-height-menu";
import { DataGridViewMenu } from "@/components/data-grid/data-grid-view-menu";

<div className="flex items-center gap-2">
  <DataGridSortMenu table={table} />
  <DataGridRowHeightMenu table={table} />
  <DataGridViewMenu table={table} />
</div>

Search Functionality

const { table, ...gridProps } = useDataGrid({
  data,
  columns,
  enableSearch: true, // Enables Ctrl+F search
});

Row Management

const onRowAdd = React.useCallback(() => {
  const newRow = {
    id: `${Date.now()}`,
    // ... other default values
  };
  setData(prev => [...prev, newRow]);
  
  return {
    rowIndex: data.length,
    columnId: "name", // Focus this column after adding
  };
}, [data.length]);

<DataGrid 
  table={table} 
  {...gridProps} 
  onRowAdd={onRowAdd}
/>

Keyboard Shortcuts

The Data Grid supports Excel-like keyboard navigation:

KeyDescription
Navigate between cells
EnterEnter edit mode or confirm edit
EscapeCancel edit or exit edit mode
TabMove to next cell (right)
ShiftTabMove to previous cell (left)
CtrlCCopy selected cells
CtrlVPaste clipboard content
DeleteClear cell content or delete selected rows
F2Enter edit mode for current cell
CtrlASelect all cells
CtrlZUndo last action
CtrlYRedo last action
CtrlFOpen search dialog

Context Menu Actions

Right-click on cells or rows to access context menu options:

  • Insert Row Above/Below: Add new rows
  • Delete Row(s): Remove selected rows
  • Copy/Cut/Paste: Clipboard operations
  • Sort Ascending/Descending: Sort by column
  • Filter: Apply column filters
  • Hide Column: Toggle column visibility

API Reference

useDataGrid

A hook for initializing the data grid with state management and editing capabilities.

Prop

Type

DataGrid

The main data grid component with virtualization and editing capabilities.

Prop

Type

DataGridColumnHeader

Custom header component for columns with sorting and context menu.

Prop

Type

DataGridCell

Individual cell component with editing capabilities.

Prop

Type

DataGridRow

Row component that renders virtualized rows.

Prop

Type

DataGridSearch

Search component with keyboard shortcuts and navigation.

Prop

Type

DataGridContextMenu

Context menu component for row and cell actions.

Prop

Type

Performance Considerations

  • Virtualization: Only visible rows and columns are rendered
  • Debounced Updates: Cell edits are debounced to prevent excessive re-renders
  • Memoization: Column definitions and data should be memoized
  • Lazy Loading: Consider implementing server-side pagination for large datasets

Accessibility

The Data Grid follows WAI-ARIA guidelines for grid widgets:

  • Full keyboard navigation support
  • Screen reader announcements for cell changes
  • Focus management during editing
  • Proper ARIA labels and roles
  • High contrast mode support

Examples

Basic Editable Grid

A simple grid with text and select cell editing capabilities.

Component data-grid-basic-demo not found in registry.

Advanced Grid with Multiple Cell Types

A comprehensive grid showcasing various cell types including text, number, select, checkbox, and date cells.

Component data-grid-advanced-demo not found in registry.

Custom Cell Renderers

For more complex use cases, you can create custom cell renderers by extending the cell variants or creating completely custom components.

Credits

  • Built on top of TanStack Table for table state management
  • Uses TanStack Virtual for virtualization
  • Inspired by modern spreadsheet applications like Airtable and Notion