import {
DataGridSelectable,
DataGridSelectableCell,
DataGridSelectableRow,
DataGridSelectableRowGroup,
} from '@vega-ui/react';
<DataGridSelectable aria-label='Quarter' defaultSelected='q2' defaultActive='q2'>
<DataGridSelectableRowGroup>
{[['q1', 'q2'], ['q3', 'q4']].map((cells, row) => (
<DataGridSelectableRow key={cells[0]} row={row}>
{cells.map((cell, col) => <DataGridSelectableCell key={cell} col={col} cellKey={cell}>{cell.toUpperCase()}</DataGridSelectableCell>)}- 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
DataGridSelectable extends DataGrid with active-cell management and single, multiple, or range selection. It is the behavioral selection layer underneath higher-level picker subsystems.
Imports
import {
DataGridSelectable,
DataGridSelectableCell,
DataGridSelectableRow,
DataGridSelectableRowGroup,
type DataGridDisabled,
type DataGridSelectableCellProps,
type DataGridSelectableProps,
type DataGridSelectableRowGroupProps,
type DataGridSelectableRowProps,
type DataGridSelection,
} from '@vega-ui/react';Exported Types
DataGridSelectableProps<K, S>DataGridSelectableRowGroupPropsDataGridSelectableRowPropsDataGridSelectableCellPropsDataGridSelection = 'single' | 'multiple' | 'range'DataGridDisabled<K>
Minimal Composition
import {
DataGridSelectable,
DataGridSelectableCell,
DataGridSelectableRow,
DataGridSelectableRowGroup,
} from '@vega-ui/react';
import './examples.css';
export default function MinimalComposition() {
return (
<DataGridSelectable aria-label='Quarter' defaultActive='q1' className='selectableGrid'>
<DataGridSelectableRowGroup>Required Parts
DataGridSelectable: root selection engine and active-cell coordinatorDataGridSelectableRowGroup: row grouping scopeDataGridSelectableRow: one row in the gridDataGridSelectableCell: selectable cell surface
Optional Parts
- custom
apiRef, comparators, range resolver, and disabled maps for advanced behaviors
Composition Order
DataGridSelectableDataGridSelectableRowGroupDataGridSelectableRowDataGridSelectableCell
Variants
- Selection:
single,multiple,range - Navigation wrapping:
horizontal,vertical,both - Constraints:
disabled,exclude,from,to, custom comparators
Edge Cases
- Range expansion uses pointer and keyboard logic that should be tested in the real grid shape.
excludeDisabledchanges how disabled cells affect navigation and range resolution.fromandtoconstrain the selectable space, not just the visible layout.
Common Mistakes
- Using complex selection semantics without a stable key model.
- Forgetting that range logic depends on compare and resolve-range behavior.
- Treating it like a presentational grid instead of a selection engine.
Examples
Basic
Basic: reporting quarter
Single selection with meaningful keys: every cell gets a cellKey such as FY2025-Q3, which is what defaultSelected, defaultActive and onSelectCell work with. DataGridSelectable has no visuals of its own, so the selected look comes from [aria-selected='true'] in the example stylesheet.
import {
DataGridSelectable,
DataGridSelectableCell,
DataGridSelectableRow,
DataGridSelectableRowGroup,
} from '@vega-ui/react';
import './examples.css';
const years = [2024, 2025, 2026];
const quarters = ['Q1', 'Q2', 'Q3', 'Q4'];
export default function QuarterSelectorGrid() {Controlled/Stateful
Controlled/Stateful: interview availability
selection='multiple' with the selected keys owned by the parent: the summary and the "Clear" button read and reset the same array. The grid does not set aria-multiselectable for you, so it is passed explicitly, and empty cells get their accessible name from aria-label.
import { useState } from 'react';
import {
Button,
DataGridSelectable,
DataGridSelectableCell,
DataGridSelectableRow,
DataGridSelectableRowGroup,
Text,
} from '@vega-ui/react';
import './examples.css';
const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'];Form/Integration
Form/Integration: delivery window in a form
The grid is not a form control, so the selected key travels in a hidden input and is read with FormData on submit. Submitting without a choice shows an error linked to the grid with aria-describedby; picking a slot clears it, and the reset button clears the state through onReset. Full windows come from disabled.
import { FormEvent, useState } from 'react';
import {
Button,
DataGridSelectable,
DataGridSelectableCell,
DataGridSelectableRow,
DataGridSelectableRowGroup,
HelperText,
Text,
} from '@vega-ui/react';
import './examples.css';
Layout/Overlay
Layout/Overlay: slot grid in a popover
PopoverContent mounts its children only while open, so the grid starts fresh every time: defaultActive={slot ?? 'Mon 10:00'} puts the tab stop on the current choice. Picking a slot closes the popover, and the trigger shows the result.
import { useState } from 'react';
import {
Button,
DataGridSelectable,
DataGridSelectableCell,
DataGridSelectableRow,
DataGridSelectableRowGroup,
Popover,
PopoverContent,
PopoverTrigger,
} from '@vega-ui/react';
import './examples.css';Disabled
Disabled: booked rooms
Keys listed in disabled cannot be selected and get aria-disabled; with the default excludeDisabled they are also skipped by the arrow keys. Pass excludeDisabled={false} when users should still be able to land on them and hear why they are unavailable. The label adds "booked" for screen readers.
import {
DataGridSelectable,
DataGridSelectableCell,
DataGridSelectableRow,
DataGridSelectableRowGroup,
} from '@vega-ui/react';
import './examples.css';
const rooms = ['Atlas', 'Borealis', 'Cosmos'];
const hours = ['09:00', '10:00', '11:00', '12:00', '13:00'];
const booked = ['Atlas 10:00', 'Atlas 11:00', 'Borealis 09:00', 'Cosmos 12:00', 'Cosmos 13:00'];
Edge
Edge: booking window with from and to
from and to bound the selectable keys through compare. ISO dates compare correctly as strings, so days outside 5–24 March become disabled and unreachable without a custom comparator. The range is two keys, check-in and check-out.
import { useState } from 'react';
import {
DataGridSelectable,
DataGridSelectableCell,
DataGridSelectableRow,
DataGridSelectableRowGroup,
Text,
} from '@vega-ui/react';
import './examples.css';
const weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
const weeks = [0, 1, 2, 3];Edge: every key of the range
By default a range is stored as its two ends. resolveRange receives both ends with their grid coordinates and the grid itself, so it can walk the cells in between: here onSelectCell gets every working day of the sprint, and weekends never appear because they are not in the grid.
import { useState } from 'react';
import {
DataGridSelectable,
DataGridSelectableCell,
DataGridSelectableRow,
DataGridSelectableRowGroup,
Text,
type DataGridApiRef,
type DataGridResolveValue,
} from '@vega-ui/react';
import './examples.css';
Anatomy
Overview
DataGridSelectable is a compound behavioral grid. It layers selection and active-cell logic on top of DataGrid and exposes row-group, row, and cell parts for composition.
Required Parts
DataGridSelectable: root behavioral gridDataGridSelectableRowGroup: group of rows, optionally scopedDataGridSelectableRow: one matrix rowDataGridSelectableCell: one selectable cell
Optional Parts
apiRef: inspect or control the underlying gridresolveRange,compare,equals: advanced selection behavior hooksdisabled,exclude,from,to: selection constraints
Composition Order
DataGridSelectableDataGridSelectableRowGroupDataGridSelectableRowDataGridSelectableCell
Valid Composition Patterns
- dense selection grids with stable row and column keys
- constrained subranges with
fromandto - range selection with a custom resolver when the key model is not purely sequential
Invalid Composition Patterns
- unstable cell keys across renders
- using presentational children that do not preserve cell semantics
- expecting selection behavior from
DataGridwithout the selectable wrapper