Selection
Select a single cell, a range of adjacent cells, or multiple non-adjacent ranges of cells.
Use the selection API to control how users select cells — single cells, ranges, columns, or rows — and to read or set selections programmatically.
Overview
Selection enables you to select a single cell or ranges of cells within Handsontable. Once selected, you can retrieve data from the cell, edit the cell’s contents, or change the style of the cell.
Basic configuration
With this feature, you can select single cells or ranges of cells across a grid. Easily retrieve the coordinates of the selected cells to clear or change the cells’ content.
Use ⌘ on Mac or Ctrl on Windows to select non-adjacent ranges of cells.
Click a column header to select all cells in that column. Click a row header to select all cells in that row. Both require colHeaders or rowHeaders to be enabled.
Select ranges
There are different modes in which you can use this plugin. Choose between selecting a single cell, a range of adjacent cells, and multiple ranges of non-contiguous cells.
Possible values of selectionMode:
single- You can select a single cell.range- You can select multiple cells within a single rangeselected.multiple- Multiple non-contiguous ranges of cells can be selected.
import { useRef, useState, useEffect } from 'react';import { HotTable } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modulesregisterAllModules();
const options = [ { value: 'single', label: 'Single selection' }, { value: 'range', label: 'Range selection' }, { value: 'multiple', label: 'Multiple ranges selection' },];
const ExampleComponent = () => { const dropdownRef = useRef(null); const [isOpen, setIsOpen] = useState(false); const [selected, setSelected] = useState('multiple');
useEffect(() => { const handleOutsideClick = (e) => { if (dropdownRef.current && !dropdownRef.current.contains(e.target)) { setIsOpen(false); } }; const handleEscape = (e) => { if (e.key === 'Escape') { setIsOpen(false); } };
document.addEventListener('click', handleOutsideClick); document.addEventListener('keydown', handleEscape);
return () => { document.removeEventListener('click', handleOutsideClick); document.removeEventListener('keydown', handleEscape); }; }, []);
const handleSelect = (value) => { setSelected(value); setIsOpen(false); };
const selectedLabel = options.find((o) => o.value === selected)?.label;
return ( <> <div className="example-controls-container"> <div className="controls"> <div className="theme-dropdown" ref={dropdownRef}> <button className="theme-dropdown-trigger" type="button" aria-haspopup="listbox" aria-expanded={isOpen} onClick={() => setIsOpen(!isOpen)} > <span>{selectedLabel}</span> <svg className="theme-dropdown-chevron" aria-hidden="true" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M6 9l6 6l6 -6"/></svg> </button> {isOpen && ( <ul className="theme-dropdown-menu" role="listbox"> {options.map((opt) => ( <li key={opt.value} role="option" aria-selected={selected === opt.value} onClick={() => handleSelect(opt.value)} > {opt.label} </li> ))} </ul> )} </div> </div> </div> <HotTable data={[ ['A1', 'B1', 'C1', 'D1', 'E1', 'F1', 'G1', 'H1', 'I1'], ['A2', 'B2', 'C2', 'D2', 'E2', 'F2', 'G2', 'H2', 'I2'], ['A3', 'B3', 'C3', 'D3', 'E3', 'F3', 'G3', 'H3', 'I3'], ['A4', 'B4', 'C4', 'D4', 'E4', 'F4', 'G4', 'H4', 'I4'], ['A5', 'B5', 'C5', 'D5', 'E5', 'F5', 'G5', 'H5', 'I5'], ['A6', 'B6', 'C6', 'D6', 'E6', 'F6', 'G6', 'H6', 'I6'], ['A7', 'B7', 'C7', 'D7', 'E7', 'F7', 'G7', 'H7', 'I7'], ['A8', 'B8', 'C8', 'D8', 'E8', 'F8', 'G8', 'H8', 'I8'], ['A9', 'B9', 'C9', 'D9', 'E9', 'F9', 'G9', 'H9', 'I9'], ]} width="auto" height="auto" colWidths={100} rowHeaders={true} colHeaders={true} selectionMode={selected} autoWrapRow={true} autoWrapCol={true} licenseKey="non-commercial-and-evaluation" /> </> );};
export default ExampleComponent;import { useRef, useState, useEffect } from 'react';import { HotTable } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modulesregisterAllModules();
const options: { value: string; label: string }[] = [ { value: 'single', label: 'Single selection' }, { value: 'range', label: 'Range selection' }, { value: 'multiple', label: 'Multiple ranges selection' },];
const ExampleComponent = () => { const dropdownRef = useRef<HTMLDivElement>(null); const [isOpen, setIsOpen] = useState(false); const [selected, setSelected] = useState('multiple');
useEffect(() => { const handleOutsideClick = (e: MouseEvent) => { if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) { setIsOpen(false); } }; const handleEscape = (e: KeyboardEvent) => { if (e.key === 'Escape') { setIsOpen(false); } };
document.addEventListener('click', handleOutsideClick); document.addEventListener('keydown', handleEscape);
return () => { document.removeEventListener('click', handleOutsideClick); document.removeEventListener('keydown', handleEscape); }; }, []);
const handleSelect = (value: string) => { setSelected(value); setIsOpen(false); };
const selectedLabel = options.find((o) => o.value === selected)?.label;
return ( <> <div className="example-controls-container"> <div className="controls"> <div className="theme-dropdown" ref={dropdownRef}> <button className="theme-dropdown-trigger" type="button" aria-haspopup="listbox" aria-expanded={isOpen} onClick={() => setIsOpen(!isOpen)} > <span>{selectedLabel}</span> <svg className="theme-dropdown-chevron" aria-hidden="true" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M6 9l6 6l6 -6"/></svg> </button> {isOpen && ( <ul className="theme-dropdown-menu" role="listbox"> {options.map((opt) => ( <li key={opt.value} role="option" aria-selected={selected === opt.value} onClick={() => handleSelect(opt.value)} > {opt.label} </li> ))} </ul> )} </div> </div> </div> <HotTable data={[ ['A1', 'B1', 'C1', 'D1', 'E1', 'F1', 'G1', 'H1', 'I1'], ['A2', 'B2', 'C2', 'D2', 'E2', 'F2', 'G2', 'H2', 'I2'], ['A3', 'B3', 'C3', 'D3', 'E3', 'F3', 'G3', 'H3', 'I3'], ['A4', 'B4', 'C4', 'D4', 'E4', 'F4', 'G4', 'H4', 'I4'], ['A5', 'B5', 'C5', 'D5', 'E5', 'F5', 'G5', 'H5', 'I5'], ['A6', 'B6', 'C6', 'D6', 'E6', 'F6', 'G6', 'H6', 'I6'], ['A7', 'B7', 'C7', 'D7', 'E7', 'F7', 'G7', 'H7', 'I7'], ['A8', 'B8', 'C8', 'D8', 'E8', 'F8', 'G8', 'H8', 'I8'], ['A9', 'B9', 'C9', 'D9', 'E9', 'F9', 'G9', 'H9', 'I9'], ]} width="auto" height="auto" colWidths={100} rowHeaders={true} colHeaders={true} selectionMode={selected as 'single' | 'range' | 'multiple'} autoWrapRow={true} autoWrapCol={true} licenseKey="non-commercial-and-evaluation" /> </> );};
export default ExampleComponent;Get data from the selected ranges
To retrieve the selected cells as an array of arrays, you use the getSelected() or getSelectedRange() methods.
import { useRef, useState } from 'react';import { HotTable } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modulesregisterAllModules();
const ExampleComponent = () => { const hotRef = useRef(null); const [output, setOutput] = useState(''); const getButtonClickCallback = () => { const hot = hotRef.current?.hotInstance; const selected = hot?.getSelected() || []; let data = [];
if (selected.length === 1) { data = hot?.getData(...selected[0]) || []; } else { for (let i = 0; i < selected.length; i += 1) { const item = selected[i];
data.push(hot?.getData(...item)); } }
setOutput(JSON.stringify(data)); };
return ( <> <div className="example-controls-container"> <div className="controls"> <button id="getButton" onClick={() => getButtonClickCallback()}> Get data </button> </div> <output className="console" id="output"> {output} </output> </div> <HotTable ref={hotRef} data={[ ['A1', 'B1', 'C1', 'D1', 'E1', 'F1', 'G1', 'H1', 'I1'], ['A2', 'B2', 'C2', 'D2', 'E2', 'F2', 'G2', 'H2', 'I2'], ['A3', 'B3', 'C3', 'D3', 'E3', 'F3', 'G3', 'H3', 'I3'], ['A4', 'B4', 'C4', 'D4', 'E4', 'F4', 'G4', 'H4', 'I4'], ['A5', 'B5', 'C5', 'D5', 'E5', 'F5', 'G5', 'H5', 'I5'], ['A6', 'B6', 'C6', 'D6', 'E6', 'F6', 'G6', 'H6', 'I6'], ['A7', 'B7', 'C7', 'D7', 'E7', 'F7', 'G7', 'H7', 'I7'], ['A8', 'B8', 'C8', 'D8', 'E8', 'F8', 'G8', 'H8', 'I8'], ['A9', 'B9', 'C9', 'D9', 'E9', 'F9', 'G9', 'H9', 'I9'], ]} width="auto" height="auto" colWidths={100} rowHeaders={true} colHeaders={true} outsideClickDeselects={false} selectionMode="multiple" // 'single', 'range' or 'multiple', autoWrapRow={true} autoWrapCol={true} licenseKey="non-commercial-and-evaluation" /> </> );};
export default ExampleComponent;import { useRef, useState } from 'react';import Handsontable from 'handsontable/base';import { HotTable, HotTableRef } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modulesregisterAllModules();
const ExampleComponent = () => { const hotRef = useRef<HotTableRef>(null); const [output, setOutput] = useState('');
const getButtonClickCallback = () => { const hot = hotRef.current?.hotInstance; const selected = hot?.getSelected() || []; let data: Handsontable.CellValue[] = [];
if (selected.length === 1) { data = hot?.getData(...selected[0]!) || []; } else { for (let i = 0; i < selected.length; i += 1) { const item = selected[i];
data.push(hot?.getData(...item)); } }
setOutput(JSON.stringify(data)); };
return ( <> <div className="example-controls-container"> <div className="controls"> <button id="getButton" onClick={() => getButtonClickCallback()}> Get data </button> </div> <output className="console" id="output"> {output} </output> </div> <HotTable ref={hotRef} data={[ ['A1', 'B1', 'C1', 'D1', 'E1', 'F1', 'G1', 'H1', 'I1'], ['A2', 'B2', 'C2', 'D2', 'E2', 'F2', 'G2', 'H2', 'I2'], ['A3', 'B3', 'C3', 'D3', 'E3', 'F3', 'G3', 'H3', 'I3'], ['A4', 'B4', 'C4', 'D4', 'E4', 'F4', 'G4', 'H4', 'I4'], ['A5', 'B5', 'C5', 'D5', 'E5', 'F5', 'G5', 'H5', 'I5'], ['A6', 'B6', 'C6', 'D6', 'E6', 'F6', 'G6', 'H6', 'I6'], ['A7', 'B7', 'C7', 'D7', 'E7', 'F7', 'G7', 'H7', 'I7'], ['A8', 'B8', 'C8', 'D8', 'E8', 'F8', 'G8', 'H8', 'I8'], ['A9', 'B9', 'C9', 'D9', 'E9', 'F9', 'G9', 'H9', 'I9'], ]} width="auto" height="auto" colWidths={100} rowHeaders={true} colHeaders={true} outsideClickDeselects={false} selectionMode="multiple" // 'single', 'range' or 'multiple', autoWrapRow={true} autoWrapCol={true} licenseKey="non-commercial-and-evaluation" /> </> );};
export default ExampleComponent;Modify the selected cells
You may want to delete, format, or otherwise change the selected cells. For example, you can change a value or add CSS classes to the selected cells using the demo below.
import { useRef } from 'react';import { HotTable } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modulesregisterAllModules();
const ExampleComponent = () => { const hotRef = useRef(null); const buttonClickCallback = () => { const hot = hotRef.current?.hotInstance; const selected = hot?.getSelected() || [];
hot?.suspendRender();
for (let index = 0; index < selected.length; index += 1) { const [row1, column1, row2, column2] = selected[index]; const startRow = Math.max(Math.min(row1, row2), 0); const endRow = Math.max(row1, row2); const startCol = Math.max(Math.min(column1, column2), 0); const endCol = Math.max(column1, column2);
for (let rowIndex = startRow; rowIndex <= endRow; rowIndex += 1) { for (let columnIndex = startCol; columnIndex <= endCol; columnIndex += 1) { hot?.setDataAtCell(rowIndex, columnIndex, 'data changed'); hot?.setCellMeta(rowIndex, columnIndex, 'className', 'c-red'); } } }
hot?.render(); hot?.resumeRender(); };
return ( <> <div className="example-controls-container"> <div className="controls"> <button id="set-data-action" onClick={() => buttonClickCallback()}> Click to modify the selected cells </button> </div> </div> <HotTable ref={hotRef} data={[ ['A1', 'B1', 'C1', 'D1', 'E1', 'F1', 'G1', 'H1', 'I1'], ['A2', 'B2', 'C2', 'D2', 'E2', 'F2', 'G2', 'H2', 'I2'], ['A3', 'B3', 'C3', 'D3', 'E3', 'F3', 'G3', 'H3', 'I3'], ['A4', 'B4', 'C4', 'D4', 'E4', 'F4', 'G4', 'H4', 'I4'], ['A5', 'B5', 'C5', 'D5', 'E5', 'F5', 'G5', 'H5', 'I5'], ['A6', 'B6', 'C6', 'D6', 'E6', 'F6', 'G6', 'H6', 'I6'], ['A7', 'B7', 'C7', 'D7', 'E7', 'F7', 'G7', 'H7', 'I7'], ['A8', 'B8', 'C8', 'D8', 'E8', 'F8', 'G8', 'H8', 'I8'], ['A9', 'B9', 'C9', 'D9', 'E9', 'F9', 'G9', 'H9', 'I9'], ]} width="auto" height="auto" colWidths={100} rowHeaders={true} colHeaders={true} outsideClickDeselects={false} selectionMode="multiple" // 'single', 'range' or 'multiple', autoWrapRow={true} autoWrapCol={true} licenseKey="non-commercial-and-evaluation" /> </> );};
export default ExampleComponent;import { useRef } from 'react';import { HotTable, HotTableRef } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modulesregisterAllModules();
const ExampleComponent = () => { const hotRef = useRef<HotTableRef>(null);
const buttonClickCallback = () => { const hot = hotRef.current?.hotInstance; const selected = hot?.getSelected() || [];
hot?.suspendRender();
for (let index = 0; index < selected.length; index += 1) { const [row1, column1, row2, column2] = selected[index]; const startRow = Math.max(Math.min(row1, row2), 0); const endRow = Math.max(row1, row2); const startCol = Math.max(Math.min(column1, column2), 0); const endCol = Math.max(column1, column2);
for (let rowIndex = startRow; rowIndex <= endRow; rowIndex += 1) { for (let columnIndex = startCol; columnIndex <= endCol; columnIndex += 1) { hot?.setDataAtCell(rowIndex, columnIndex, 'data changed'); hot?.setCellMeta(rowIndex, columnIndex, 'className', 'c-red'); } } }
hot?.render(); hot?.resumeRender(); };
return ( <> <div className="example-controls-container"> <div className="controls"> <button id="set-data-action" onClick={() => buttonClickCallback()}> Click to modify the selected cells </button> </div> </div> <HotTable ref={hotRef} data={[ ['A1', 'B1', 'C1', 'D1', 'E1', 'F1', 'G1', 'H1', 'I1'], ['A2', 'B2', 'C2', 'D2', 'E2', 'F2', 'G2', 'H2', 'I2'], ['A3', 'B3', 'C3', 'D3', 'E3', 'F3', 'G3', 'H3', 'I3'], ['A4', 'B4', 'C4', 'D4', 'E4', 'F4', 'G4', 'H4', 'I4'], ['A5', 'B5', 'C5', 'D5', 'E5', 'F5', 'G5', 'H5', 'I5'], ['A6', 'B6', 'C6', 'D6', 'E6', 'F6', 'G6', 'H6', 'I6'], ['A7', 'B7', 'C7', 'D7', 'E7', 'F7', 'G7', 'H7', 'I7'], ['A8', 'B8', 'C8', 'D8', 'E8', 'F8', 'G8', 'H8', 'I8'], ['A9', 'B9', 'C9', 'D9', 'E9', 'F9', 'G9', 'H9', 'I9'], ]} width="auto" height="auto" colWidths={100} rowHeaders={true} colHeaders={true} outsideClickDeselects={false} selectionMode="multiple" // 'single', 'range' or 'multiple', autoWrapRow={true} autoWrapCol={true} licenseKey="non-commercial-and-evaluation" /> </> );};
export default ExampleComponent;.c-red { color: red;}Style the selection area
You can change the background color of selected cells using CSS. The base selection color is defined in the .area class.
When using multiple non-adjacent selections (Cmd/Ctrl + click), each additional selection layer receives a numbered class: area-1 for the second layer, area-2 for the third, and so on. Each class is cumulative — a cell in the second layer has both area and area-1.
The example below customizes the color of each selection layer using these CSS classes.
import { HotTable } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';import './example4.css';
// register Handsontable's modulesregisterAllModules();
const ExampleComponent = () => { return ( <HotTable data={[ ['A1', 'B1', 'C1', 'D1', 'E1', 'F1', 'G1', 'H1', 'I1'], ['A2', 'B2', 'C2', 'D2', 'E2', 'F2', 'G2', 'H2', 'I2'], ['A3', 'B3', 'C3', 'D3', 'E3', 'F3', 'G3', 'H3', 'I3'], ['A4', 'B4', 'C4', 'D4', 'E4', 'F4', 'G4', 'H4', 'I4'], ['A5', 'B5', 'C5', 'D5', 'E5', 'F5', 'G5', 'H5', 'I5'], ['A6', 'B6', 'C6', 'D6', 'E6', 'F6', 'G6', 'H6', 'I6'], ]} width="auto" height="auto" colWidths={100} rowHeaders={true} colHeaders={true} outsideClickDeselects={false} selectionMode="multiple" autoWrapRow={true} autoWrapCol={true} licenseKey="non-commercial-and-evaluation" /> );};
export default ExampleComponent;import { HotTable } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';import './example4.css';
// register Handsontable's modulesregisterAllModules();
const ExampleComponent = () => { return ( <HotTable data={[ ['A1', 'B1', 'C1', 'D1', 'E1', 'F1', 'G1', 'H1', 'I1'], ['A2', 'B2', 'C2', 'D2', 'E2', 'F2', 'G2', 'H2', 'I2'], ['A3', 'B3', 'C3', 'D3', 'E3', 'F3', 'G3', 'H3', 'I3'], ['A4', 'B4', 'C4', 'D4', 'E4', 'F4', 'G4', 'H4', 'I4'], ['A5', 'B5', 'C5', 'D5', 'E5', 'F5', 'G5', 'H5', 'I5'], ['A6', 'B6', 'C6', 'D6', 'E6', 'F6', 'G6', 'H6', 'I6'], ]} width="auto" height="auto" colWidths={100} rowHeaders={true} colHeaders={true} outsideClickDeselects={false} selectionMode="multiple" autoWrapRow={true} autoWrapCol={true} licenseKey="non-commercial-and-evaluation" /> );};
export default ExampleComponent;/* First selection layer — applied to every selected cell */#example4 td.area { background-color: rgba(75, 137, 255, 0.2);}
/* Second non-adjacent selection layer (Ctrl/Cmd + click) */#example4 td.area.area-1 { background-color: rgba(75, 137, 255, 0.4);}
/* Third non-adjacent selection layer */#example4 td.area.area-2 { background-color: rgba(75, 137, 255, 0.6);}Unfortunately, there is no easy way to change the border color of the selection.
Resize a selection with handles
When you set selectionHandles to true, hovering over a selected range shows a pill-shaped handle at the midpoint of each edge. Drag a handle to move that edge and resize the selection. The handles adjust the selected area only — they do not move, fill, or change any cell data.
To enable selection handles:
selectionHandles: true,This option applies at the grid level and defaults to false.
Limitations:
- Handles appear on desktop only. Touch devices keep their existing native selection handles and are not affected by this option.
- The option has no effect when
selectionModeis'single'. - Handles are not shown for full-row, full-column, or select-all selections.
- A handle is not shown on an edge that is flush with the grid boundary or that lands on a frozen-pane line (
fixedRowsTop,fixedRowsBottom,fixedColumnsStart). - The handles are pointer-only — there is no keyboard equivalent. To resize a selection with the keyboard, use Shift+Arrow.
import { useEffect, useRef } from 'react';import { HotTable } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';// register Handsontable's modulesregisterAllModules();const data = [ ['Ana García', 'Engineering', 'Senior Engineer', 95000, 'Madrid', 12], ['James Okafor', 'Marketing', 'Product Manager', 88000, 'Lagos', 8], ['Li Wei', 'Engineering', 'Frontend Dev', 82000, 'Shanghai', 5], ['Maria Santos', 'HR', 'HR Specialist', 71000, 'Lisbon', 3], ['David Kim', 'Engineering', 'Backend Dev', 85000, 'Seoul', 7], ['Emma Wilson', 'Marketing', 'SEO Analyst', 68000, 'London', 2], ['Ahmed Hassan', 'Finance', 'Controller', 92000, 'Cairo', 10], ['Sara Johansson', 'Engineering', 'QA Engineer', 78000, 'Stockholm', 6],];const ExampleComponent = () => { const hotRef = useRef(null); // Pre-select an interior range so the handles are immediately visible. useEffect(() => { hotRef.current?.hotInstance?.selectCell(1, 1, 4, 3); }, []); return (<HotTable ref={hotRef} data={data} colHeaders={['Name', 'Department', 'Role', 'Salary', 'City', 'Tenure']} rowHeaders={true} width="auto" height="auto" selectionHandles={true} autoWrapRow={true} autoWrapCol={true} licenseKey="non-commercial-and-evaluation"/>);};export default ExampleComponent;import { useEffect, useRef } from 'react';import { HotTable, HotTableRef } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modulesregisterAllModules();
const data = [ ['Ana García', 'Engineering', 'Senior Engineer', 95000, 'Madrid', 12], ['James Okafor', 'Marketing', 'Product Manager', 88000, 'Lagos', 8], ['Li Wei', 'Engineering', 'Frontend Dev', 82000, 'Shanghai', 5], ['Maria Santos', 'HR', 'HR Specialist', 71000, 'Lisbon', 3], ['David Kim', 'Engineering', 'Backend Dev', 85000, 'Seoul', 7], ['Emma Wilson', 'Marketing', 'SEO Analyst', 68000, 'London', 2], ['Ahmed Hassan', 'Finance', 'Controller', 92000, 'Cairo', 10], ['Sara Johansson', 'Engineering', 'QA Engineer', 78000, 'Stockholm', 6],];
const ExampleComponent = () => { const hotRef = useRef<HotTableRef>(null);
// Pre-select an interior range so the handles are immediately visible. useEffect(() => { hotRef.current?.hotInstance?.selectCell(1, 1, 4, 3); }, []);
return ( <HotTable ref={hotRef} data={data} colHeaders={['Name', 'Department', 'Role', 'Salary', 'City', 'Tenure']} rowHeaders={true} width="auto" height="auto" selectionHandles={true} autoWrapRow={true} autoWrapCol={true} licenseKey="non-commercial-and-evaluation" /> );};
export default ExampleComponent;Move a selection by dragging
When you set moveCells to true, hovering the border of a selected cell range shows a grab cursor. Dragging that border moves the block’s data — cell values and formatting — to the new location. Hold Ctrl (Windows) or ⌘ (Mac) during the drag to copy instead of move. Press Escape to cancel a drag before releasing.
When the formulas plugin is active, formula references adjust automatically on move — the same way they do in Excel.
To enable drag-to-move:
moveCells: true,This option applies at the grid level and defaults to false.
Limitations:
- Drag-to-move works on a single contiguous cell range only. It has no effect on full-row, full-column, select-all, or multiple selections.
- The target must stay within the grid. Neither the target nor the source may overlap read-only cells, because a move has to clear the source. Copying with Ctrl or ⌘ leaves the source in place, so a read-only source cell blocks a move but not a copy.
- Drag-to-move is hidden when
disableVisualSelectionis set. - A move that would split a merged cell is blocked.
Hooks:
beforeMoveCellsfires before the data relocates. Returnfalsefrom the handler to cancel the move.afterMoveCellsfires after the data has been relocated.
To move or copy a range programmatically, call hot.getPlugin('moveCells').moveCellRange(sourceRange, targetTopLeft, isCopy).
import { useEffect, useRef } from 'react';import { HotTable } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';// register Handsontable's modulesregisterAllModules();const data = [ ['Ana García', 'Engineering', 'Senior Engineer', 95000, 'Madrid', 12], ['James Okafor', 'Marketing', 'Product Manager', 88000, 'Lagos', 8], ['Li Wei', 'Engineering', 'Frontend Dev', 82000, 'Shanghai', 5], ['Maria Santos', 'HR', 'HR Specialist', 71000, 'Lisbon', 3], ['David Kim', 'Engineering', 'Backend Dev', 85000, 'Seoul', 7], ['Emma Wilson', 'Marketing', 'SEO Analyst', 68000, 'London', 2], ['Ahmed Hassan', 'Finance', 'Controller', 92000, 'Cairo', 10], ['Sara Johansson', 'Engineering', 'QA Engineer', 78000, 'Stockholm', 6],];const ExampleComponent = () => { const hotRef = useRef(null); // Pre-select an interior range so the move border is immediately discoverable. useEffect(() => { hotRef.current?.hotInstance?.selectCell(1, 1, 3, 3); }, []); return (<HotTable ref={hotRef} data={data} colHeaders={['Name', 'Department', 'Role', 'Salary', 'City', 'Tenure']} rowHeaders={true} width="auto" height="auto" moveCells={true} autoWrapRow={true} autoWrapCol={true} licenseKey="non-commercial-and-evaluation"/>);};export default ExampleComponent;import { useEffect, useRef } from 'react';import { HotTable, HotTableRef } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modulesregisterAllModules();
const data = [ ['Ana García', 'Engineering', 'Senior Engineer', 95000, 'Madrid', 12], ['James Okafor', 'Marketing', 'Product Manager', 88000, 'Lagos', 8], ['Li Wei', 'Engineering', 'Frontend Dev', 82000, 'Shanghai', 5], ['Maria Santos', 'HR', 'HR Specialist', 71000, 'Lisbon', 3], ['David Kim', 'Engineering', 'Backend Dev', 85000, 'Seoul', 7], ['Emma Wilson', 'Marketing', 'SEO Analyst', 68000, 'London', 2], ['Ahmed Hassan', 'Finance', 'Controller', 92000, 'Cairo', 10], ['Sara Johansson', 'Engineering', 'QA Engineer', 78000, 'Stockholm', 6],];
const ExampleComponent = () => { const hotRef = useRef<HotTableRef>(null);
// Pre-select an interior range so the move border is immediately discoverable. useEffect(() => { hotRef.current?.hotInstance?.selectCell(1, 1, 3, 3); }, []);
return ( <HotTable ref={hotRef} data={data} colHeaders={['Name', 'Department', 'Role', 'Salary', 'City', 'Tenure']} rowHeaders={true} width="auto" height="auto" moveCells={true} autoWrapRow={true} autoWrapCol={true} licenseKey="non-commercial-and-evaluation" /> );};
export default ExampleComponent;Select cells programmatically
Use selectCell() to select a single cell or a range of cells from code. Pass the start and end row/column indices to define a range. Use deselectCell() to clear the selection.
import { useRef } from 'react';import { HotTable } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modulesregisterAllModules();
const ExampleComponent = () => { const hotRef = useRef(null); const selectCell = () => { // Select a single cell: row 1, col 1 (B2) hotRef.current?.hotInstance?.selectCell(1, 1); };
const selectRange = () => { // Select a range: row 1, col 1 to row 3, col 3 (B2:D4) hotRef.current?.hotInstance?.selectCell(1, 1, 3, 3); };
const deselect = () => { hotRef.current?.hotInstance?.deselectCell(); };
return ( <> <div className="example-controls-container"> <div className="controls"> <button onClick={selectCell}>Select cell B2</button> <button onClick={selectRange}>Select range B2:D4</button> <button onClick={deselect}>Deselect</button> </div> </div> <HotTable ref={hotRef} data={[ ['A1', 'B1', 'C1', 'D1', 'E1', 'F1'], ['A2', 'B2', 'C2', 'D2', 'E2', 'F2'], ['A3', 'B3', 'C3', 'D3', 'E3', 'F3'], ['A4', 'B4', 'C4', 'D4', 'E4', 'F4'], ['A5', 'B5', 'C5', 'D5', 'E5', 'F5'], ['A6', 'B6', 'C6', 'D6', 'E6', 'F6'], ]} width="auto" height="auto" colWidths={100} rowHeaders={true} colHeaders={true} outsideClickDeselects={false} autoWrapRow={true} autoWrapCol={true} licenseKey="non-commercial-and-evaluation" /> </> );};
export default ExampleComponent;import { useRef } from 'react';import { HotTable, HotTableRef } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modulesregisterAllModules();
const ExampleComponent = () => { const hotRef = useRef<HotTableRef>(null);
const selectCell = () => { // Select a single cell: row 1, col 1 (B2) hotRef.current?.hotInstance?.selectCell(1, 1); };
const selectRange = () => { // Select a range: row 1, col 1 to row 3, col 3 (B2:D4) hotRef.current?.hotInstance?.selectCell(1, 1, 3, 3); };
const deselect = () => { hotRef.current?.hotInstance?.deselectCell(); };
return ( <> <div className="example-controls-container"> <div className="controls"> <button onClick={selectCell}>Select cell B2</button> <button onClick={selectRange}>Select range B2:D4</button> <button onClick={deselect}>Deselect</button> </div> </div> <HotTable ref={hotRef} data={[ ['A1', 'B1', 'C1', 'D1', 'E1', 'F1'], ['A2', 'B2', 'C2', 'D2', 'E2', 'F2'], ['A3', 'B3', 'C3', 'D3', 'E3', 'F3'], ['A4', 'B4', 'C4', 'D4', 'E4', 'F4'], ['A5', 'B5', 'C5', 'D5', 'E5', 'F5'], ['A6', 'B6', 'C6', 'D6', 'E6', 'F6'], ]} width="auto" height="auto" colWidths={100} rowHeaders={true} colHeaders={true} outsideClickDeselects={false} autoWrapRow={true} autoWrapCol={true} licenseKey="non-commercial-and-evaluation" /> </> );};
export default ExampleComponent;Jump across the grid’s edges
When you use keyboard navigation to cross an edge of the grid, you can set cell selection to jump to the opposite edge.
Jump across vertical edges
To enable jumping across the left and right edges:
- Set the
autoWrapRowconfiguration option totrue.
To jump across a vertical edge:
- When cell selection is on a row’s first cell, press ←.
- When cell selection is on a row’s last cell, press →, or press Tab.
Jump across horizontal edges
To enable jumping across the top and bottom edges:
- Set the
autoWrapColconfiguration option totrue.
To jump across a horizontal edge:
- When cell selection is on a column’s first cell, press ↑.
- When cell selection is on a column’s last cell, press ↓, or press Enter.
Use the checkboxes in the demo below to toggle autoWrapRow and autoWrapCol, then select a cell and use the keyboard to test the wrap-around behavior at the grid’s edges.
import { useRef, useState } from 'react';import { HotTable } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modulesregisterAllModules();
const ExampleComponent = () => { const hotRef = useRef(null); const [autoWrapRow, setAutoWrapRow] = useState(true); const [autoWrapCol, setAutoWrapCol] = useState(true);
return ( <> <div className="example-controls-container"> <div className="controls"> <label> <input type="checkbox" checked={autoWrapRow} onChange={(event) => setAutoWrapRow(event.target.checked)} />{' '} Wrap at the left/right edges (autoWrapRow) </label> <label> <input type="checkbox" checked={autoWrapCol} onChange={(event) => setAutoWrapCol(event.target.checked)} />{' '} Wrap at the top/bottom edges (autoWrapCol) </label> </div> <p> Select a cell, then press <kbd>Tab</kbd> or <kbd>→</kbd> at the end of a row, or <kbd>Enter</kbd> or{' '} <kbd>↓</kbd> at the bottom of a column, to see the effect. </p> </div> <HotTable ref={hotRef} data={[ ['SKU-4821', 'Harbor Goods', 'Electronics', 142], ['SKU-0093', 'Alpine Supply Co.', 'Apparel', 67], ['SKU-2210', 'Harbor Goods', 'Electronics', 0], ['SKU-7734', 'Nordic Traders', 'Home Goods', 58], ['SKU-1145', 'Alpine Supply Co.', 'Apparel', 213], ]} colHeaders={['SKU', 'Supplier', 'Category', 'Quantity']} width="auto" height="auto" rowHeaders={true} autoWrapRow={autoWrapRow} autoWrapCol={autoWrapCol} licenseKey="non-commercial-and-evaluation" /> </> );};
export default ExampleComponent;import { useRef, useState } from 'react';import { HotTable, HotTableRef } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modulesregisterAllModules();
const ExampleComponent = () => { const hotRef = useRef<HotTableRef>(null); const [autoWrapRow, setAutoWrapRow] = useState(true); const [autoWrapCol, setAutoWrapCol] = useState(true);
return ( <> <div className="example-controls-container"> <div className="controls"> <label> <input type="checkbox" checked={autoWrapRow} onChange={(event) => setAutoWrapRow(event.target.checked)} />{' '} Wrap at the left/right edges (autoWrapRow) </label> <label> <input type="checkbox" checked={autoWrapCol} onChange={(event) => setAutoWrapCol(event.target.checked)} />{' '} Wrap at the top/bottom edges (autoWrapCol) </label> </div> <p> Select a cell, then press <kbd>Tab</kbd> or <kbd>→</kbd> at the end of a row, or <kbd>Enter</kbd> or{' '} <kbd>↓</kbd> at the bottom of a column, to see the effect. </p> </div> <HotTable ref={hotRef} data={[ ['SKU-4821', 'Harbor Goods', 'Electronics', 142], ['SKU-0093', 'Alpine Supply Co.', 'Apparel', 67], ['SKU-2210', 'Harbor Goods', 'Electronics', 0], ['SKU-7734', 'Nordic Traders', 'Home Goods', 58], ['SKU-1145', 'Alpine Supply Co.', 'Apparel', 213], ]} colHeaders={['SKU', 'Supplier', 'Category', 'Quantity']} width="auto" height="auto" rowHeaders={true} autoWrapRow={autoWrapRow} autoWrapCol={autoWrapCol} licenseKey="non-commercial-and-evaluation" /> </> );};
export default ExampleComponent;Result
Users can select cells using the configured mode — single cell, range, or multiple ranges. Programmatic selections take effect immediately and fire the relevant selection hooks.
Related keyboard shortcuts
| Windows | macOS | Action | Excel | Sheets |
|---|---|---|---|---|
| Ctrl+A | ⌘+A | Select all cells | ✓ | ✓ |
| Ctrl+Shift+Space | ⌘+⇧+Space | Select all cells and headers | ✓ | ✓ |
| Ctrl+Space | ⌃+Space | Select the entire column | ✓ | ✓ |
| Shift+Space | ⇧+Space | Select the entire row | ✓ | ✓ |
| Ctrl+Shift+↑ | ⌘+⇧+↑ | Extend the selection to the first cell of the current column** | ✓ | ✓ |
| Ctrl+Shift+↓ | ⌘+⇧+↓ | Extend the selection to the last cell of the current column** | ✓ | ✓ |
| Ctrl+Shift+← | ⌘+⇧+← | Extend the selection to the leftmost cell of the current row** | ✓ | ✓ |
| Ctrl+Shift+→ | ⌘+⇧+→ | Extend the selection to the rightmost cell of the current row** | ✓ | ✓ |
| Shift + Arrow keys | ⇧ + Arrow keys | Extend the selection by one cell | ✓ | ✓ |
| Shift+Home | ⇧+Home | Extend the selection to the first non-frozen cell of the current row* | ✓ | ✗ |
| Shift+End | ⇧+End | Extend the selection to the last non-frozen cell of the current row* | ✗ | ✗ |
| Shift+Page Up | ⇧+Page Up | Extend the selection by one screen up | ✓ | ✓ |
| Shift+Page Down | ⇧+Page Down | Extend the selection by one screen down | ✓ | ✓ |
| Ctrl+Enter | ⌘+Enter | Fill the selected range of cells with the value of the active cell | ✗ | ✓ |
| Delete | Delete | Clear the contents of the selected cells | ✓ | ✓ |
| Backspace | Backspace | Clear the contents of the selected cells | ✓ | ✓ |
* This action depends on your layout direction.
** In case of multiple selection layers, only the last selection layer gets extended.
Related API reference
Configuration options
Core methods
Hooks
Plugins