Row headers
Use default row headers (1, 2, 3), or set them to custom values provided by an array or a function.
Overview
Row headers are gray-colored columns that are used to label each row. By default, these headers are filled with numbers displayed in ascending order.
To turn the headers on, set the option rowHeaders to true.
Row headers as an array
An array of labels can be used to set the rowHeaders as shown in the example below:
import { HotTable } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modulesregisterAllModules();
const ExampleComponent = () => { return ( <HotTable data={[ [42000, 31000, 11000], [45500, 33200, 12300], [48700, 35100, 13600], [51200, 36800, 14400], [54800, 38900, 15900], [57300, 40100, 17200], ]} colHeaders={['Revenue', 'Expenses', 'Profit']} rowHeaders={['January', 'February', 'March', 'April', 'May', 'June']} rowHeaderWidth={80} height="auto" autoWrapRow={true} autoWrapCol={true} licenseKey="non-commercial-and-evaluation" /> );};
export default ExampleComponent;import { HotTable } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modulesregisterAllModules();
const ExampleComponent = () => { return ( <HotTable data={[ [42000, 31000, 11000], [45500, 33200, 12300], [48700, 35100, 13600], [51200, 36800, 14400], [54800, 38900, 15900], [57300, 40100, 17200], ]} colHeaders={['Revenue', 'Expenses', 'Profit']} rowHeaders={['January', 'February', 'March', 'April', 'May', 'June']} rowHeaderWidth={80} height="auto" autoWrapRow={true} autoWrapCol={true} licenseKey="non-commercial-and-evaluation" /> );};
export default ExampleComponent;Row headers as a function
The rowHeaders can also be populated using a function as shown in the example below:
import { HotTable } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modulesregisterAllModules();
const ExampleComponent = () => { return ( <HotTable data={[ [42000, 31000, 11000], [45500, 33200, 12300], [48700, 35100, 13600], [51200, 36800, 14400], [54800, 38900, 15900], [57300, 40100, 17200], ]} colHeaders={['Revenue', 'Expenses', 'Profit']} rowHeaders={(index) => { return `Row ${index + 1}`; }} height="auto" autoWrapRow={true} autoWrapCol={true} licenseKey="non-commercial-and-evaluation" /> );};
export default ExampleComponent;import { HotTable } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modulesregisterAllModules();
const ExampleComponent = () => { return ( <HotTable data={[ [42000, 31000, 11000], [45500, 33200, 12300], [48700, 35100, 13600], [51200, 36800, 14400], [54800, 38900, 15900], [57300, 40100, 17200], ]} colHeaders={['Revenue', 'Expenses', 'Profit']} rowHeaders={(index) => { return `Row ${index + 1}`; }} height="auto" autoWrapRow={true} autoWrapCol={true} licenseKey="non-commercial-and-evaluation" /> );};
export default ExampleComponent;Row header width
Row headers have a fixed width. When you use custom row labels, a label longer than that width is clipped, and the header does not grow to fit it the way a column does.
You have two ways to deal with this: set the width yourself, or let Handsontable measure it.
Set the width yourself
To control the header size, set rowHeaderWidth to one of the following:
- A number - set the same width for every row header.
- A string - set the same width, written as a pixel size:
'80'or'80px'. - An array - set different widths for individual row header levels.
The width is a number of pixels. A string that states a pixel size works too, so a value coming from
an attribute, a JSON config, or a framework template still applies. You can mix both forms inside the
array. A value that states no pixel count, such as '50%' or '20em', is ignored - the header keeps
its default width, and Handsontable reports the value once in the browser console. Inside an array
this applies per level, so one unreadable entry does not disturb the levels around it.
A negative number is kept as it is, because numbers behave exactly as they did before this option read strings. A negative string is rejected, so a typo cannot collapse the header.
The Row headers as an array example uses custom labels together with rowHeaderWidth: 80.
Size the header to its content
To size the row header column to its longest label, enable the
AutoRowHeaderSize plugin by setting
autoRowHeaderSize to true. That is all you need: the plugin
takes the header’s width over, so any rowHeaderWidth you already set is ignored while it is enabled.
In the example below, the row labels are too long for the default header width. Turning the plugin on is enough to make every label fit.
import { HotTable } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modulesregisterAllModules();
const ExampleComponent = () => { return ( <HotTable data={[ [42000, 45500, 48700, 51200], [18300, 19100, 20400, 21600], [23700, 26400, 28300, 29600], [9800, 10200, 11100, 11700], [13900, 16200, 17200, 17900], [11200, 13100, 13900, 14500], ]} colHeaders={['Q1', 'Q2', 'Q3', 'Q4']} rowHeaders={[ 'Revenue', 'Cost of goods sold', 'Gross profit', 'Operating expenses', 'Operating income', 'Net income', ]} // Size the row header column to its longest label. autoRowHeaderSize={true} height="auto" autoWrapRow={true} autoWrapCol={true} licenseKey="non-commercial-and-evaluation" /> );};
export default ExampleComponent;import { HotTable } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modulesregisterAllModules();
const ExampleComponent = () => { return ( <HotTable data={[ [42000, 45500, 48700, 51200], [18300, 19100, 20400, 21600], [23700, 26400, 28300, 29600], [9800, 10200, 11100, 11700], [13900, 16200, 17200, 17900], [11200, 13100, 13900, 14500], ]} colHeaders={['Q1', 'Q2', 'Q3', 'Q4']} rowHeaders={[ 'Revenue', 'Cost of goods sold', 'Gross profit', 'Operating expenses', 'Operating income', 'Net income', ]} // Size the row header column to its longest label. autoRowHeaderSize={true} height="auto" autoWrapRow={true} autoWrapCol={true} licenseKey="non-commercial-and-evaluation" /> );};
export default ExampleComponent;The plugin is off by default, because turning it on would change the row header width of every grid
that uses custom labels. This mirrors AutoRowSize, which is also opt-in,
while AutoColumnSize - bounded by the number of columns - is on by
default.
The plugin never makes a header narrower than the default width, so a grid of short labels looks the
same as it does without the plugin. It also leaves a little room around the longest label, so the
text never sits flush against the cell border. That matters most for a row header you draw
yourself: the grid’s own renderer wraps its label in a padded element, but a renderer pushed through
afterGetRowHeaderRenderers writes straight into the
cell and has no padding of its own.
Tuning the measurement
Instead of true, you can pass an object to change how the measurement runs. All three properties
are optional, and the defaults suit most grids:
| Property | Possible values | Description |
|---|---|---|
samplingRatio | A number | How many labels of the same length get rendered and measured. Default: 3. |
allowSampleDuplicates | true | false | Whether two rows carrying the same label are both measured. Default: false. |
syncLimit | A number | a percent string | How many rows are read before the first paint. Default: 500. |
autoRowHeaderSize: { samplingRatio: 5, allowSampleDuplicates: true, syncLimit: 1000,},samplingRatio exists because reading a label is cheap but laying one out is not. The plugin
groups the labels by how many characters they have and renders only a few from each group. Raising
the number measures more of them, which is slightly slower but harder to fool: in a proportional
font WWW is wider than iii, so measuring more same-length labels makes it less likely that the
widest one is missed. Lower it to do less work.
allowSampleDuplicates decides what happens when two rows carry the same label. By default the
label is measured once, because the same text normally renders to the same width. Set it to true
when that is not true of your grid - a row header that is indented per row, as
nestedRows does, draws the same label at a different width
depending on how deep the row sits, so measuring only the first one would come out too narrow.
Raise samplingRatio along with it. Labels are grouped by length and only samplingRatio of each
group get measured, so at the default of 3 a fourth row carrying the same label is still left out
- which on a deep tree can be the widest one of them.
syncLimit decides how much of the work happens before the grid first appears. Finding the
longest label means reading every row header once, which on a large grid takes long enough to be
felt. So the first syncLimit rows are read straight away, and the rest are read in the browser’s
idle time, in short bursts of a few milliseconds each - however many rows fit, which depends on how
many row header columns the grid draws. A header can therefore widen a moment after the grid
appears. While that is running a header only ever widens, so its width never jumps back and forth.
Raise the limit to have more of it settled before the first paint, or pass a percent string such as
'40%' to scale it with the number of rows.
Editing a cell does not start that work again. Only the rows that changed are read, because a row header label can be built from cell values - a data column used as the label, for instance. That reading also waits for an idle moment, so a header widened by an edit grows a moment after the edit rather than during it.
An edit can make a header wider, but never narrower. Working out that a header should shrink means
finding the new longest label, which no shortcut avoids, so it waits for the next full pass. Loading
or replacing the data starts one, as does adding or removing a row, sorting, hiding or showing a row,
switching the theme, and a recalculation by the Formulas plugin.
Setting autoRowHeaderSize to false, or leaving it out, keeps the fixed-width headers.
Add more row header columns
A grid can render more than one row header. The
afterGetRowHeaderRenderers hook hands you the array of
renderers that draw them, one renderer per column. Push your own renderer onto that array, and it
draws one more row header column to the right of the previous one.
The example below pushes two renderers next to the default numbering, so the grid gets three row
header columns. Each column holds labels of a different length, and autoRowHeaderSize measures
every column on its own: the numbering column stays narrow, while each label column gets the width
its own text needs. Turn the plugin off and all three columns fall back to the same fixed width,
which clips the longer labels.
import { HotTable } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modulesregisterAllModules();
const groups = ['Top line', 'Direct costs', 'Margin', 'Overhead', 'Margin', 'Bottom line'];const lineItems = [ 'Revenue', 'Cost of goods sold', 'Gross profit', 'Operating expenses', 'Operating income', 'Net income',];
const ExampleComponent = () => { return ( <HotTable data={[ [42000, 45500, 48700, 51200], [18300, 19100, 20400, 21600], [23700, 26400, 28300, 29600], [9800, 10200, 11100, 11700], [13900, 16200, 17200, 17900], [11200, 13100, 13900, 14500], ]} colHeaders={['Q1', 'Q2', 'Q3', 'Q4']} rowHeaders={true} // Add two more row header columns next to the numbering. afterGetRowHeaderRenderers={(renderers) => { // A renderer gets the renderable row index. It matches the visual index when no rows are hidden. renderers.push((renderableRowIndex, TH) => { TH.innerText = groups[renderableRowIndex]; }); renderers.push((renderableRowIndex, TH) => { TH.innerText = lineItems[renderableRowIndex]; }); }} // Measure each of the three row header columns on its own. autoRowHeaderSize={true} height="auto" autoWrapRow={true} autoWrapCol={true} licenseKey="non-commercial-and-evaluation" /> );};
export default ExampleComponent;import { HotTable } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modulesregisterAllModules();
const groups = ['Top line', 'Direct costs', 'Margin', 'Overhead', 'Margin', 'Bottom line'];const lineItems = [ 'Revenue', 'Cost of goods sold', 'Gross profit', 'Operating expenses', 'Operating income', 'Net income',];
const ExampleComponent = () => { return ( <HotTable data={[ [42000, 45500, 48700, 51200], [18300, 19100, 20400, 21600], [23700, 26400, 28300, 29600], [9800, 10200, 11100, 11700], [13900, 16200, 17200, 17900], [11200, 13100, 13900, 14500], ]} colHeaders={['Q1', 'Q2', 'Q3', 'Q4']} rowHeaders={true} // Add two more row header columns next to the numbering. afterGetRowHeaderRenderers={(renderers: Array<(renderableRowIndex: number, TH: HTMLElement) => void>) => { // A renderer gets the renderable row index. It matches the visual index when no rows are hidden. renderers.push((renderableRowIndex, TH) => { TH.innerText = groups[renderableRowIndex]; }); renderers.push((renderableRowIndex, TH) => { TH.innerText = lineItems[renderableRowIndex]; }); }} // Measure each of the three row header columns on its own. autoRowHeaderSize={true} height="auto" autoWrapRow={true} autoWrapCol={true} licenseKey="non-commercial-and-evaluation" /> );};
export default ExampleComponent;Each renderer is called with the row’s renderable index - the index Handsontable renders by. It is
the same as the visual index
until rows are hidden. If your grid hides rows, translate the index with
getVisualFromRenderableIndex() before you look
a label up by it.
Bind rows with headers
You can bind row numbers with row headers. This is used mostly to differentiate two business cases in which Handsontable is most often used.
-
When moving a row in a typical grid-like application, the numbers in the row headers remain intact. Only the content is moved.
-
In a data grid, each row has its unique ID. Therefore, the column header should follow its row whenever it changes its position in the grid.
Basic example
To enable the plugin, set the bindRowsWithHeaders property to true. Move the rows in the example below to see what this plugin does.
Possible values:
true- Enables the plugin.strict- Enables the plugin and the order of indexes is not reorganized after the operation such as hiding or moving rows.loose- Enables the plugin and the order of indexes is re-organized after the operation such as hiding or moving rows.
import { HotTable } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modulesregisterAllModules();
const ExampleComponent = () => { // Generate an array of arrays with a dummy data const generateData = (rows = 3, columns = 7, additionalRows = true) => { let counter = 0; const array2d = [...new Array(rows)].map((_) => [...new Array(columns)].map((_) => counter++));
if (additionalRows) { array2d.push([]); array2d.push([]); }
return array2d; };
return ( <HotTable data={generateData()} colHeaders={true} rowHeaders={true} height="auto" contextMenu={true} manualRowMove={true} bindRowsWithHeaders={true} autoWrapRow={true} autoWrapCol={true} licenseKey="non-commercial-and-evaluation" /> );};
export default ExampleComponent;import { HotTable } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modulesregisterAllModules();
const ExampleComponent = () => { // Generate an array of arrays with a dummy data const generateData = (rows = 3, columns = 7, additionalRows = true) => { let counter = 0;
const array2d = [...new Array(rows)].map((_) => [...new Array(columns)].map((_) => counter++));
if (additionalRows) { array2d.push([]); array2d.push([]); }
return array2d; };
return ( <HotTable data={generateData()} colHeaders={true} rowHeaders={true} height="auto" contextMenu={true} manualRowMove={true} bindRowsWithHeaders={true} autoWrapRow={true} autoWrapCol={true} licenseKey="non-commercial-and-evaluation" /> );};
export default ExampleComponent;Tree grid
A tree grid enables you to represent the nested data structures within the data grid. To learn more about this feature, see the Row parent-child page.
Related articles
Related guides
Configuration options
Core methods
Hooks
Plugins