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
- Oklab and OkLCh — comes from the design tokens
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
DataGridPropsDataGridCellKeyDataGridCoordinatesDataGridWrapDataGridResolveValueDataGridExcludeDataGridExcludeResolverDataGridApiRefDataGridScope
Minimal Composition
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 modelDataGridRowGroup: row grouping containerDataGridRow: row registrationDataGridCell: focusable or navigable cell
Optional Parts
- custom cell content inside
DataGridCell - controlled active-cell logic in the parent layer
apiReffor advanced focus coordination and measurement
Composition Order
Typical composition:
DataGridDataGridRowGroupDataGridRowDataGridCell
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.
scopematters when multiple logical grids or paged content share one interaction model.
Common Mistakes
- Using
DataGridfor static tables that should stayTable. - 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.
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.
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.
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.
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.
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.
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.
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.
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:
DataGridDataGridRowGroupDataGridRowDataGridCell
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.
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.