Skip to content
v2.5.0
English
Русский
Documentation

DataGrid

DataGrid is a compound component for keyboard-aware grid navigation across rows and cells.
Design
Build
Ship
Done
Review
Planned
data-grid.tsx · 21 lines
import {
  DataGrid,
  DataGridCell,
  DataGridRow,
  DataGridRowGroup,
} from '@vega-ui/react';

<DataGrid aria-label='Release checklist'>
  <DataGridRowGroup>
    <DataGridRow row={0}>
      <DataGridCell col={0}>Design</DataGridCell>
      <DataGridCell col={1}>Build</DataGridCell>
Browser support
Widely available
  • Chrome 111
  • Edge 111
  • Firefox 113
  • Safari 15.4
  • Safari on iOS 15.4
Bundle size
Tested in
52 tests

Summary

DataGrid is a compound component for keyboard-aware grid navigation across rows and cells. It is the foundation layer underneath DataGridSelectable and DataGridPicker.

Imports

import {
  DataGrid,
  DataGridRowGroup,
  DataGridRow,
  DataGridCell,
  type DataGridProps,
  type DataGridCellKey,
  type DataGridCoordinates,
  type DataGridWrap,
} from '@vega-ui/react';

Exported Types

  • DataGridProps
  • DataGridCellKey
  • DataGridCoordinates
  • DataGridWrap
  • DataGridResolveValue
  • DataGridExclude
  • DataGridExcludeResolver
  • DataGridApiRef
  • DataGridScope

Minimal Composition

minimal-composition.tsx · 26 lines
import {
  DataGrid,
  DataGridCell,
  DataGridRow,
  DataGridRowGroup,
} from '@vega-ui/react';
import './examples.css';

export default function MinimalComposition() {
  return (
    <DataGrid aria-label='Release checklist' defaultActive='0:0' className='dataGridSheet'>
      <DataGridRowGroup>

Required Parts

  • DataGrid: root keyboard and active-cell model
  • DataGridRowGroup: row grouping container
  • DataGridRow: row registration
  • DataGridCell: focusable or navigable cell

Optional Parts

  • custom cell content inside DataGridCell
  • controlled active-cell logic in the parent layer
  • apiRef for advanced focus coordination and measurement

Composition Order

Typical composition:

  1. DataGrid
  2. DataGridRowGroup
  3. DataGridRow
  4. DataGridCell

Variants

  • Wrap modes from DataGridWrap
  • Uncontrolled default active cell versus controlled active cell
  • Plain grid usage versus selectable or picker-like derived families built above it

Edge Cases

  • Sparse grids need exclusion rules that still match the visible layout.
  • Interactive widgets inside cells can disrupt keyboard flow.
  • Large datasets may need virtualization outside the component.
  • scope matters when multiple logical grids or paged content share one interaction model.

Common Mistakes

  • Using DataGrid for static tables that should stay Table.
  • Ignoring exclusion logic when the visual layout is sparse.
  • Embedding complex controls inside cells without retesting keyboard behavior.

Examples

Basic

Basic: on-call rota

A weekly rota with real column and row headers. Headers are passed as role='columnheader' and role='rowheader' on DataGridCell, so screen readers announce the engineer and the day while the arrow keys move cell by cell. The grid has its own accessible name, and defaultActive decides which cell takes the single tab stop.

team-schedule.tsx · 41 lines
import {
  DataGrid,
  DataGridCell,
  DataGridRow,
  DataGridRowGroup,
} from '@vega-ui/react';
import './examples.css';

const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'];

const rota = [
  { name: 'Maya Chen', shifts: ['Day', 'Day', 'Off', 'Night', 'Night'] },

Controlled/Stateful

Controlled/Stateful: active cell owned by the parent

The parent owns active and shows details of the focused shift next to the grid. Setting active only moves the tab stop ("Reset tab stop"); to move focus as well, focus the cell element through apiRef ("Go to Friday"), and the grid reports the new key through onChangeActive.

controlled-active-cell.tsx · 63 lines
import { useRef, useState } from 'react';
import {
  Button,
  DataGrid,
  DataGridCell,
  DataGridRow,
  DataGridRowGroup,
  Text,
  type DataGridApiRef,
} from '@vega-ui/react';
import './examples.css';

Form/Integration

Form/Integration: edit a cell in a dialog form

Cells react to Enter and click with your own handlers: here they open a Dialog with a form, and the submitted FormData updates the rota. DataGrid itself has no activation behavior, so the action key and its handler belong to the cells.

edit-a-cell-in-a-dialog.tsx · 124 lines
import { FormEvent, KeyboardEvent, useState, type CSSProperties } from 'react';
import { XIcon } from '@vega-ui/icons';
import {
  Button,
  DataGrid,
  DataGridCell,
  DataGridRow,
  DataGridRowGroup,
  Dialog,
  DialogClose,
  DialogContent,
  Heading,

Layout/Overlay

Layout/Overlay: scrollable grid with a sticky header

168 cells in a scroll container with a sticky header row. Focus scrolls the active cell into view, and scroll-padding-block-start keeps it from hiding under the header. rowDelta={6} makes PageUp/PageDown jump six hours instead of to the first or last row.

scrollable-grid-with-sticky-header.tsx · 50 lines
import {
  DataGrid,
  DataGridCell,
  DataGridRow,
  DataGridRowGroup,
} from '@vega-ui/react';
import './examples.css';

const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
const hours = Array.from({ length: 24 }, (_, hour) => `${String(hour).padStart(2, '0')}:00`);

const getAgents = (hour: number, day: number) => {

Disabled

Disabled: cells skipped by the keyboard

exclude takes a predicate, so closed slots stay visible but the arrow keys and Home/End jump over them. The grid does not mark them for assistive technology: pass aria-disabled and a descriptive label yourself.

excluded-cells.tsx · 43 lines
import {
  DataGrid,
  DataGridCell,
  DataGridRow,
  DataGridRowGroup,
} from '@vega-ui/react';
import './examples.css';

const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'];
const hours = ['09:00', '10:00', '11:00', '12:00'];
const closed = ['0:2', '1:2', '2:2', '3:2', '3:4'];

Edge

Edge: ragged rows across a whole year

365 cells where every month row has its own length. Rendering only the days that exist is enough: ArrowDown from 31 January skips February and lands on 31 March, and End goes to the last day of the current month.

days-of-the-year.tsx · 55 lines
import { useState } from 'react';
import {
  DataGrid,
  DataGridCell,
  DataGridRow,
  DataGridRowGroup,
  Text,
} from '@vega-ui/react';
import './examples.css';

const year = 2026;
const months = Array.from({ length: 12 }, (_, month) => month);

Edge: wrap modes

Switch wrap and try the arrow keys at the edges: horizontal continues on the next or previous row, vertical continues at the top of the next column, both does both, and without wrap the edges stop movement.

wrap-modes.tsx · 63 lines
import { useState } from 'react';
import {
  DataGrid,
  DataGridCell,
  DataGridRow,
  DataGridRowGroup,
  SegmentedControl,
  SegmentedControlIndicator,
  SegmentedControlItem,
  SegmentedControlItemInput,
  type DataGridWrap,
} from '@vega-ui/react';

Edge: right-to-left layout

In a right-to-left context ArrowLeft and ArrowRight follow the text direction: ArrowRight moves toward the start of the row. The grid reads the computed direction, so dir='rtl' on any ancestor is enough.

right-to-left.tsx · 38 lines
import {
  DataGrid,
  DataGridCell,
  DataGridRow,
  DataGridRowGroup,
} from '@vega-ui/react';
import './examples.css';

const days = ['ראשון', 'שני', 'שלישי', 'רביעי', 'חמישי'];
const shifts = [
  ['בוקר', 'בוקר', 'חופש', 'ערב', 'ערב'],
  ['ערב', 'חופש', 'בוקר', 'בוקר', 'חופש'],

Anatomy

Overview

DataGrid owns the active-cell model, wrap behavior, exclusion logic, and keyboard navigation across rows and cells.

Required Parts

DataGrid

Root provider for active-cell coordination.

DataGridRowGroup

Grouping container for rows.

DataGridRow

Row registration unit.

DataGridCell

Cell registration and navigation unit.

Optional Parts

Controlled active-cell state

The parent can own the active cell for richer product flows.

Interactive child content

Allowed, but must be tested carefully against the keyboard model.

apiRef

Optional imperative bridge for advanced grid coordination.

Composition Order

Typical composition:

  1. DataGrid
  2. DataGridRowGroup
  3. DataGridRow
  4. DataGridCell

Valid Composition Patterns

Give the grid an accessible name and an initial defaultActive (or a controlled active) key. Without it no cell has tabindex='0', and the grid cannot be reached with Tab.

minimal-composition.tsx · 26 lines
import {
  DataGrid,
  DataGridCell,
  DataGridRow,
  DataGridRowGroup,
} from '@vega-ui/react';
import './examples.css';

export default function MinimalComposition() {
  return (
    <DataGrid aria-label='Release checklist' defaultActive='0:0' className='dataGridSheet'>
      <DataGridRowGroup>

Invalid Composition Patterns

Cells without matching row or column structure

This breaks navigation assumptions and active-cell math.

Sparse layouts without exclusion logic

The keyboard model may still try to navigate through visual gaps.

Treating DataGrid as a visual table only

DataGrid is a keyboard-navigation system, not just a layout primitive.