

## Overview [#overview]

Set `interaction="spreadsheet"` when cells, rather than rows, own pointer and
keyboard interaction. The preset supports both read-only spreadsheets and
editable grids while preserving consumer-owned records and column definitions.

| Capability     | DataGrid owns                                                  | Consumer owns                                                            |
| -------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------ |
| Cell selection | Range identity, pointer/keyboard interaction, selected styling | Which columns may be selected and what product actions selection enables |
| Fill requests  | Fill handle and source/target range request                    | Fill algorithm, parsing, validation, and record mutation                 |
| Navigation     | Roving tab stop, arrow-key movement, focus visibility          | When product UI requests focus imperatively                              |
| Editing        | Edit identity, activation, and editor boundaries               | Editors, drafts, validation, mutations, and persistence                  |

Cell selection is distinct from the
[Row Selection](/components/data-grid/row-selection) preset. Opt
checkbox or action columns out of cell selection with
`enableCellSelection: false`.

## Examples [#examples]

### Cell selection [#cell-selection]

Users can click, drag, Shift-extend, or use the keyboard to select a rectangular
range. DataGrid suppresses browser text selection during drags that begin on
selectable cells, while interactive cell content retains its normal behavior.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="DataGridCellSelectionScenario" />
  </Tab>

  <Tab value="Code">
    ```tsx
    <Toolbar.Actions>
      <table.Subscribe source={table.atoms.cellSelection}>
        {(selection) => (
          <Button
            disabled={selection.length === 0}
            onClick={() => table.resetCellSelection(true)}
            variant="secondary"
          >
            Clear selection
          </Button>
        )}
      </table.Subscribe>
    </Toolbar.Actions>

    <DataGrid interaction="spreadsheet" table={table}>
      <DataGrid.Viewport aria-label="Cell selection" />
    </DataGrid>
    ```
  </Tab>
</Tabs>

### Fill requests [#fill-requests]

Passing `onFillRequest` enables the fill handle for the active range. DataGrid
reports stable row and column IDs; it never copies or mutates record values.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="DataGridFillRequestsScenario" />
  </Tab>

  <Tab value="Code">
    ```tsx
    const [fillRequest, setFillRequest] = useState<DataGridFillRequest | null>(null);

    <DataGrid
      interaction="spreadsheet"
      onFillRequest={setFillRequest}
      table={table}
    >
      <DataGrid.Viewport aria-label="Fill matter cells" />
    </DataGrid>

    <p>
      {fillRequest
        ? `Fill through ${fillRequest.target.focusRowId}`
        : "Select cells, then drag the fill handle"}
    </p>
    ```
  </Tab>
</Tabs>

### Keyboard navigation [#keyboard-navigation]

The viewport owns one roving tab stop. Arrow keys move through selectable cells
and skip columns where `cell.getCanSelect()` is false. Home and End stay within
the current row; Ctrl or Command with Home or End moves to the first or last
selectable cell. Escape clears the current cell selection and returns DOM focus
to the viewport, removing the active cell without moving focus outside the grid.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="DataGridKeyboardNavigationScenario" />
  </Tab>

  <Tab value="Code">
    ```tsx
    const columns = columnHelper.columns([
      columnHelper.accessor("name", {
        header: "Matter",
        meta: { rowHeader: true },
      }),
      columnHelper.accessor("status", { header: "Status" }),
      columnHelper.accessor("owner", { header: "Owner" }),
    ]);

    <DataGrid interaction="spreadsheet" table={table}>
      <DataGrid.Viewport aria-label="Keyboard navigation" />
    </DataGrid>
    ```
  </Tab>
</Tabs>

### Imperative focus [#imperative-focus]

Use the viewport handle when another product control must move focus into a
known cell. Identify cells with stable consumer row and column IDs.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="DataGridImperativeFocusScenario" />
  </Tab>

  <Tab value="Code">
    ```tsx
    const viewportRef = useRef<DataGridViewportHandle>(null);

    <Menu>
      <MenuTrigger render={<Button variant="secondary" />}>
        Go to matter
      </MenuTrigger>
      <MenuContent align="end">
        {matters.map((matter) => (
          <MenuItem
            key={matter.id}
            onClick={() => {
              requestAnimationFrame(() => {
                viewportRef.current?.focusCell({ rowId: matter.id, columnId: "name" });
              });
            }}
          >
            {matter.name}
          </MenuItem>
        ))}
      </MenuContent>
    </Menu>

    <DataGrid interaction="spreadsheet" table={table}>
      <DataGrid.Viewport aria-label="Imperative cell focus" ref={viewportRef} />
    </DataGrid>
    ```
  </Tab>
</Tabs>

## Editing [#editing]

Editing is optional within the spreadsheet preset. DataGrid identifies the
active cell, resolves whether it may enter edit mode, and provides editor
chrome and interaction boundaries. The consumer owns
drafts, parsing, validation, record updates, persistence, optimistic behavior,
permissions, and error recovery.

Editability resolves from the most specific configured level:

1. A column `enableEditing` value or predicate overrides the table default.
2. Otherwise the table `enableEditing` value or predicate applies.
3. Synthetic grouped, aggregate, pivot, subtotal, and total cells are read only.

Editable cells enter edit mode through double-click, Enter, F2, printable
typing, or an explicit `cell.startEditing()` call. Escape stops editing. A
consumer capture handler may call `preventDefault()` to reserve an interaction.

### Basic editing [#basic-editing]

Enable editing at table level, render a consumer-owned editor in the editable
column, and commit changes into consumer state.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="DataGridEditingScenario" />
  </Tab>

  <Tab value="Code">
    ```tsx
    columnHelper.accessor("name", {
      header: "Matter",
      meta: { rowHeader: true },
      cell: ({ cell }) => (
        <EditableTextCell
          cell={cell}
          onCommit={(name) => {
            setData((current) =>
              current.map((matter) =>
                matter.id === cell.row.id ? { ...matter, name } : matter,
              ),
            );
            return true;
          }}
        />
      ),
    });

    const table = useDataGridTable(
      { columns, data, enableEditing: true, getRowId: (row) => row.id },
      () => null,
    );

    <DataGrid interaction="spreadsheet" table={table}>
      <DataGrid.Viewport aria-label="Editable matters" />
    </DataGrid>
    ```
  </Tab>
</Tabs>

Subscribe to `table.atoms.editingCell` inside the editor so only cells that
depend on editing state update:

```tsx
function EditableTextCell({ cell, onCommit }) {
  return (
    <Subscribe
      source={cell.table.atoms.editingCell}
      selector={(editing) =>
        editing?.rowId === cell.row.id && editing.columnId === cell.column.id
      }
    >
      {(isEditing) =>
        isEditing ? (
          <DataGrid.Editor>
            <Input /* consumer draft and commit handlers */ />
          </DataGrid.Editor>
        ) : (
          cell.getValue()
        )
      }
    </Subscribe>
  );
}
```

### Edit eligibility [#edit-eligibility]

This table is read only by default. Its Owner column opts in only for rows whose
status is Review, demonstrating the table default, column override, and
per-cell predicate without mixing in validation.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="DataGridEditEligibilityScenario" />
  </Tab>

  <Tab value="Code">
    ```tsx
    columnHelper.accessor("owner", {
      header: "Owner",
      enableEditing: (cell) => cell.row.original.status === "Review",
      cell: ({ cell }) => <EditableTextCell cell={cell} onCommit={updateOwner} />,
    });

    const table = useDataGridTable(
      {
        columns,
        data,
        enableEditing: false,
        getRowId: (row) => row.id,
      },
      () => null,
    );
    ```
  </Tab>
</Tabs>

### Validation [#validation]

Eligibility decides whether an editor may open. Validation is a separate
consumer concern in the commit path. Return `false` when validation fails to
leave the editor open; return `true` after applying the accepted value.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="DataGridValidationScenario" />
  </Tab>

  <Tab value="Code">
    ```tsx
    <EditableTextCell
      cell={cell}
      onCommit={(owner) => {
        if (!owner.trim()) {
          setMessage("Owner is required. The draft was not committed.");
          return false;
        }

        updateMatter(cell.row.id, { owner: owner.trim() });
        return true;
      }}
    />
    ```
  </Tab>
</Tabs>

### Editing state [#editing-state]

| Field         | Type                                                          | Description                                             |
| ------------- | ------------------------------------------------------------- | ------------------------------------------------------- |
| `rowId`       | `string`                                                      | Stable row ID of the active cell.                       |
| `columnId`    | `string`                                                      | Column ID of the active cell.                           |
| `reason`      | `"consumer" \| "double-click" \| "enter" \| "f2" \| "typing"` | Interaction that started editing.                       |
| `initialText` | `string \| undefined`                                         | First printable character for type-to-replace behavior. |

## API Reference [#api-reference]

### DataGrid props [#datagrid-props]

| Prop            | Type                                     | Default | Description                                                                               |
| --------------- | ---------------------------------------- | ------- | ----------------------------------------------------------------------------------------- |
| `interaction`   | `"spreadsheet"`                          | -       | Enables cell focus, selection, range gestures, and optional editing activation. Required. |
| `onFillRequest` | `(request: DataGridFillRequest) => void` | -       | Enables the fill handle and reports the requested source and target ranges.               |

### Table options [#table-options]

| Option                           | Type                             | Default                       | Description                                                           |
| -------------------------------- | -------------------------------- | ----------------------------- | --------------------------------------------------------------------- |
| `autoResetCellSelection`         | `boolean`                        | `true`                        | Resets cell selection when the data reference changes.                |
| `enableCellSelection`            | `boolean \| ((cell) => boolean)` | `true`                        | Enables selection globally or for cells that match a predicate.       |
| `enableCellRangeSelection`       | `boolean`                        | `true`                        | Enables Shift and drag range selection.                               |
| `enableCellSelectionDrag`        | `boolean`                        | `true`                        | Enables extending a range by dragging.                                |
| `enableMultiCellRangeSelection`  | `boolean`                        | `true`                        | Enables modifier-key include and exclude ranges.                      |
| `isCellRangeSelectionEvent`      | `(event: unknown) => boolean`    | Shift-key event               | Determines whether a selection gesture extends the active range.      |
| `isMultiCellRangeSelectionEvent` | `(event: unknown) => boolean`    | Control- or Command-key event | Determines whether a gesture adds or subtracts a range.               |
| `onCellSelectionChange`          | `OnChangeFn<CellSelectionState>` | State updater                 | Receives cell-selection updates when the consumer controls the slice. |

### Column options [#column-options]

| Option                | Type      | Default | Description                                |
| --------------------- | --------- | ------- | ------------------------------------------ |
| `enableCellSelection` | `boolean` | `true`  | Allows cells in the column to be selected. |

### Selection methods [#selection-methods]

| API                               | Signature                                          | Description                                                  |
| --------------------------------- | -------------------------------------------------- | ------------------------------------------------------------ |
| `table.getFocusedCell`            | `() => Cell<DataGridFeatures, TData> \| undefined` | Returns the active cell.                                     |
| `table.getSelectedCellCount`      | `() => number`                                     | Returns the number of selected cells.                        |
| `table.getSelectedCellRangesData` | `() => unknown[][][]`                              | Returns each selected region's values as a row-major grid.   |
| `table.selectCellRange`           | `(range, options?) => void`                        | Replaces, includes, or excludes a rectangular range.         |
| `table.selectAllCells`            | `() => void`                                       | Selects every eligible cell.                                 |
| `table.setFocusedCell`            | `(rowId, columnId) => void`                        | Collapses selection to one cell address.                     |
| `table.resetCellSelection`        | `(defaultState?) => void`                          | Restores initial selection, or clears it when passed `true`. |

### DataGridCellAddress [#datagridcelladdress]

| Field      | Type     | Description             |
| ---------- | -------- | ----------------------- |
| `rowId`    | `string` | Stable consumer row ID. |
| `columnId` | `string` | Stable column ID.       |

### DataGridFillRequest [#datagridfillrequest]

| Field    | Type                 | Description                             |
| -------- | -------------------- | --------------------------------------- |
| `source` | `CellSelectionRange` | Active range before the fill gesture.   |
| `target` | `CellSelectionRange` | Requested range after the fill gesture. |

Each range stores anchor and focus row and column IDs. Apply the request to
consumer records only after product validation.

### DataGridViewportHandle [#datagridviewporthandle]

| Method         | Signature                                          | Description                                                                                                                 |
| -------------- | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `focusCell`    | `(address: DataGridCellAddress) => void`           | Focuses and reveals a selectable cell, mounting its virtual row when needed. Invalid or ineligible addresses do nothing.    |
| `scrollToCell` | `(address: DataGridCellAddress, options?) => void` | Reveals a cell and mounts its virtual row when needed. `options.align` accepts `"auto"`, `"center"`, `"end"`, or `"start"`. |

Subscribe to `table.atoms.cellSelection` when another product control needs the
current range.

### Table editing options [#table-editing-options]

| Option                | Type                               | Default       | Description                                                              |
| --------------------- | ---------------------------------- | ------------- | ------------------------------------------------------------------------ |
| `enableEditing`       | `boolean \| ((cell) => boolean)`   | `false`       | Enables editing for every eligible cell or cells that match a predicate. |
| `onEditingCellChange` | `OnChangeFn<DataGridEditingState>` | State updater | Receives editing-state updates when the consumer controls the slice.     |

### Column editing options [#column-editing-options]

| Option          | Type                             | Default       | Description                                         |
| --------------- | -------------------------------- | ------------- | --------------------------------------------------- |
| `enableEditing` | `boolean \| ((cell) => boolean)` | Table setting | Overrides edit eligibility for cells in the column. |

### Editing cell methods [#editing-cell-methods]

| Method              | Signature            | Description                                                                         |
| ------------------- | -------------------- | ----------------------------------------------------------------------------------- |
| `cell.getCanEdit`   | `() => boolean`      | Returns whether the cell can enter edit mode. Synthetic cells are always read only. |
| `cell.getIsEditing` | `() => boolean`      | Returns whether the cell is currently editing.                                      |
| `cell.startEditing` | `(details?) => void` | Requests edit mode when the cell can edit.                                          |
| `cell.stopEditing`  | `() => void`         | Clears edit mode when the cell is active.                                           |

### Editing table methods [#editing-table-methods]

| Method                   | Signature                 | Description                                                      |
| ------------------------ | ------------------------- | ---------------------------------------------------------------- |
| `table.setEditingCell`   | `(updater) => void`       | Sets or updates editing identity.                                |
| `table.resetEditingCell` | `(defaultState?) => void` | Restores initial editing state, or clears it when passed `true`. |

### DataGrid.Editor [#datagrideditor]

Visual chrome and event isolation for a consumer-owned editor. It extends
`div` props and does not own draft or commit behavior.
