Skip to content

Grid size

Set the width and height of the grid, using either absolute values or values relative to the parent container.

Set your grid’s size

You need to define the grid’s container as a starting point to initialize it. Usually, the div element becomes this container. This container should have defined dimensions as well as the rest of your layout. Handsontable supports relative units such as %, rem, em, vh, vw, and px.

Define the size in your CSS

Both width and height could be defined as inline styles or as a CSS class property. In this case, it’s important to define what should be an overflow parent properly. Handsontable looks for the closest element with overflow: auto or overflow: hidden to use it as a scrollable container. If no such element is found, a window will be used.

A container with a CSS height but no overflow does not size the grid. The grid then follows the window and grows past that container. Handsontable’s own bars (the pagination bar, the sheets bar) share the scrollable container with the grid, and the grid leaves room for them.

Pass the size in the configuration

You can pass width and height values to Handsontable as numbers or possible CSS values for the “width”/“height” properties:

<HotTable height={100} width={100} />

or

<HotTable height="75%" width="75%" />

or

<HotTable height="100px" width="100px" />

You can also pass a function to width and height. Use this when you calculate dimensions from your current layout. The function can return a number (pixels) or a CSS size string.

const getGridWidth = () => `${window.innerWidth - 64}px`;
const getGridHeight = () => 400;
<HotTable width={getGridWidth} height={getGridHeight} />

These dimensions will be set as inline styles in a container element, and overflow: hidden will be added automatically.

If container is a block element, then its parent has to have defined height. By default block element is 0px height, so 100% from 0px is still 0px.

Changes called in updateSettings() will re-render the grid with the new properties.

Compare size units

Use the dropdown in the demo below to switch the grid’s width and height between px, %, em, rem, vh, and vw, and see how the same grid responds to each unit.

JavaScript
import { useState } from 'react';
import { HotTable } from '@handsontable/react-wrapper';
import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modules
registerAllModules();
const UNIT_SIZES = {
px: { width: '600px', height: '300px' },
'%': { width: '75%', height: '75%' },
em: { width: '37.5em', height: '18.75em' },
rem: { width: '37.5rem', height: '18.75rem' },
vh: { width: '50vh', height: '50vh' },
vw: { width: '50vw', height: '50vw' },
};
const UNIT_CAPTIONS = {
px: 'A fixed pixel size, independent of any parent element or font size.',
'%': "A percentage of the parent container's size (the dashed box).",
em: "A multiple of this element's own font size.",
rem: "A multiple of the document's root font size.",
vh: "A percentage of the browser viewport's height.",
vw: "A percentage of the browser viewport's width.",
};
const data = [
['SKU-4821', 'Wireless Mouse', 'Electronics', 'Harbor Goods', 142],
['SKU-0093', 'Canvas Tote Bag', 'Apparel', 'Alpine Supply Co.', 67],
['SKU-2210', 'USB-C Hub', 'Electronics', 'Harbor Goods', 0],
['SKU-7734', 'Ceramic Mug Set', 'Home Goods', 'Nordic Traders', 58],
['SKU-1145', 'Wool Scarf', 'Apparel', 'Alpine Supply Co.', 213],
['SKU-3399', 'Bluetooth Speaker', 'Electronics', 'Harbor Goods', 84],
['SKU-5567', 'Cotton T-Shirt', 'Apparel', 'Alpine Supply Co.', 310],
['SKU-8842', 'Desk Lamp', 'Home Goods', 'Nordic Traders', 45],
['SKU-6621', 'Laptop Stand', 'Electronics', 'Harbor Goods', 29],
['SKU-4470', 'Throw Blanket', 'Home Goods', 'Nordic Traders', 76],
['SKU-9983', 'Leather Wallet', 'Apparel', 'Alpine Supply Co.', 132],
['SKU-2287', 'Wireless Charger', 'Electronics', 'Harbor Goods', 97],
];
const ExampleComponent = () => {
const [unit, setUnit] = useState('px');
const { width, height } = UNIT_SIZES[unit];
return (
<>
<div className="example-controls-container">
<div className="controls">
<label htmlFor="unitSelect">Grid size unit</label>
<select id="unitSelect" value={unit} onChange={(event) => setUnit(event.target.value)}>
{Object.keys(UNIT_SIZES).map((key) => (
<option key={key} value={key}>{key}</option>
))}
</select>
</div>
<p className="unit-caption">{UNIT_CAPTIONS[unit]}</p>
</div>
<div id="exampleParent2">
<HotTable
data={data}
colHeaders={['SKU', 'Product', 'Category', 'Supplier', 'Quantity']}
rowHeaders={true}
width={width}
height={height}
licenseKey="non-commercial-and-evaluation"
/>
</div>
</>
);
};
export default ExampleComponent;
TypeScript
import { useState } from 'react';
import { HotTable } from '@handsontable/react-wrapper';
import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modules
registerAllModules();
const UNIT_SIZES: Record<string, { width: string; height: string }> = {
px: { width: '600px', height: '300px' },
'%': { width: '75%', height: '75%' },
em: { width: '37.5em', height: '18.75em' },
rem: { width: '37.5rem', height: '18.75rem' },
vh: { width: '50vh', height: '50vh' },
vw: { width: '50vw', height: '50vw' },
};
const UNIT_CAPTIONS: Record<string, string> = {
px: 'A fixed pixel size, independent of any parent element or font size.',
'%': "A percentage of the parent container's size (the dashed box).",
em: "A multiple of this element's own font size.",
rem: "A multiple of the document's root font size.",
vh: "A percentage of the browser viewport's height.",
vw: "A percentage of the browser viewport's width.",
};
const data = [
['SKU-4821', 'Wireless Mouse', 'Electronics', 'Harbor Goods', 142],
['SKU-0093', 'Canvas Tote Bag', 'Apparel', 'Alpine Supply Co.', 67],
['SKU-2210', 'USB-C Hub', 'Electronics', 'Harbor Goods', 0],
['SKU-7734', 'Ceramic Mug Set', 'Home Goods', 'Nordic Traders', 58],
['SKU-1145', 'Wool Scarf', 'Apparel', 'Alpine Supply Co.', 213],
['SKU-3399', 'Bluetooth Speaker', 'Electronics', 'Harbor Goods', 84],
['SKU-5567', 'Cotton T-Shirt', 'Apparel', 'Alpine Supply Co.', 310],
['SKU-8842', 'Desk Lamp', 'Home Goods', 'Nordic Traders', 45],
['SKU-6621', 'Laptop Stand', 'Electronics', 'Harbor Goods', 29],
['SKU-4470', 'Throw Blanket', 'Home Goods', 'Nordic Traders', 76],
['SKU-9983', 'Leather Wallet', 'Apparel', 'Alpine Supply Co.', 132],
['SKU-2287', 'Wireless Charger', 'Electronics', 'Harbor Goods', 97],
];
const ExampleComponent = () => {
const [unit, setUnit] = useState('px');
const { width, height } = UNIT_SIZES[unit];
return (
<>
<div className="example-controls-container">
<div className="controls">
<label htmlFor="unitSelect">Grid size unit</label>
<select id="unitSelect" value={unit} onChange={(event) => setUnit(event.target.value)}>
{Object.keys(UNIT_SIZES).map((key) => (
<option key={key} value={key}>{key}</option>
))}
</select>
</div>
<p className="unit-caption">{UNIT_CAPTIONS[unit]}</p>
</div>
<div id="exampleParent2">
<HotTable
data={data}
colHeaders={['SKU', 'Product', 'Category', 'Supplier', 'Quantity']}
rowHeaders={true}
width={width}
height={height}
licenseKey="non-commercial-and-evaluation"
/>
</div>
</>
);
};
export default ExampleComponent;
CSS
#exampleParent2 {
width: 800px;
max-width: 100%;
height: 400px;
box-sizing: border-box;
border: 1px dashed var(--sl-color-gray-5, #d1d5db);
}
#exampleParent2 > div {
height: 100%;
}
.unit-caption {
margin-top: 8px;
color: var(--sl-color-text, #485164);
}

Use 'auto' sizing

Set height: 'auto' to make the grid behave like a plain block element. Handsontable writes height: auto as an inline style on the root element, and nothing else. The grid grows to fit its rows, the nearest scrolling ancestor or the page scrolls it, and off-screen rows stay virtualized.

<HotTable height="auto" />

You can combine it with width="auto" to let the grid follow its parent container’s width:

<HotTable height="auto" width="auto" />

height: 'auto' is different from leaving height unset:

SettingInline styles on rootScroll parentRow virtualization
height: 'auto'height: auto;Nearest ancestor with overflow: auto or overflow: hidden, else the windowEnabled
height: <number or CSS length>height: <value>; overflow: clip;The grid itselfEnabled
height unsetNone, Handsontable does not touch the root inline stylesNearest ancestor with overflow: auto or overflow: hidden, else the windowEnabled

The one difference between 'auto' and an unset height is the inline height: auto, which overrides a height that a stylesheet sets on the root element.

Accepted values

width and height accept the same set of values:

ValueExample
A number of pixelsheight: 500
A string with a number of pixelsheight: '500', height: '500px'
A string with a CSS unitheight: '50%', height: '75vh'
'auto'height: 'auto'
A function that returns one of the aboveheight() { return 500; }

Any other value the browser can read as a CSS length or expression ('20em', 'calc(100% - 40px)', 'var(--grid-height)') is passed through as written.

A value the browser cannot read as a size ('abc', -100, true) is ignored, and so are these CSS keywords:

  • 'inherit', 'initial', 'unset', 'revert', 'revert-layer', 'none', and 'normal', which do not set a size.
  • 'min-content', 'max-content', and 'fit-content', which size the grid to its full content, so it cannot scroll inside its box.
  • 'stretch', '-webkit-fill-available', and '-moz-available', which fill the container but read as a fixed size. Use '100%' or 'auto' instead.

An ignored value leaves the grid’s size as it was, and Handsontable prints a warning once per grid and value.

Passing null to either option through updateSettings() restores that axis to the root element’s initial inline style and leaves the other axis in place.

The two options accept the same values, but they clip differently:

  • Any height other than 'auto', including a percentage or a viewport unit, sizes the grid’s box. The grid clips both axes and scrolls inside that box, as the table above shows.
  • A width clips only when it is definite (a number, '500px', '20em'). The grid then scrolls its columns inside that width on its own, with or without a height. A relative width ('100%', '80vw', 'var(--grid-width)') leaves the horizontal overflow to the page, so the columns past it stay reachable.

Troubleshooting with 100% height

When the height option is set to 100%, there are three ways to define the container’s height. Assuming you’re creating an Handsontable instance that has 100% height and container is element with id #example.

const container = document.querySelector('#example');
const hot = new Handsontable(container, {
height: '100%',
// ...rest of config
}
  1. Set a fixed height (in pixels) directly on the example element where Handsontable is mounted
<div class="page-wrapper">
<!-- Other HTML element -->
<div id="example" style="height: 500px">
<div class="ht-root-wrapper ht-theme-main">
<div class="ht-grid">
<div class="ht-wrapper">
<!-- Table content -->
</div>
</div>
<!-- Table components -->
</div>
</div>
</div>
  1. Set a fixed height on the parent element, and then give the example itself a height of 100%
<div class="page-wrapper" style="height: 500px">
<!-- Other HTML element -->
<div id="example" style="height: 100%">
<div class="ht-root-wrapper ht-theme-main">
<div class="ht-grid">
<div class="ht-wrapper">
<!-- Table content -->
</div>
</div>
<!-- Table components -->
</div>
</div>
</div>
  1. Use flexbox on the wrapper element to make the example fill the available space
<div class="page-wrapper" style="display: flex; height: 500px">
<!-- Other HTML element -->
<div id="example" style="flex: 1">
<div class="ht-root-wrapper ht-theme-main">
<div class="ht-grid">
<div class="ht-wrapper">
<!-- Table content -->
</div>
</div>
<!-- Table components -->
</div>
</div>
</div>

When using Flexbox, the container automatically expands to fill the available space in the flex container. This is particularly useful when you want the grid to take up all the available space within its parent.

What if the size is not set

If you don’t define any dimensions, Handsontable generates as many rows and columns as needed to fill the available space.

If your grid’s contents don’t fit in the viewport, the browser’s native scrollbars are used for scrolling. For this to work properly, Handsontable’s layout direction (e.g., layoutDirection: 'rtl') must be the same as your HTML document’s layout direction (<html dir='rtl'>). Otherwise, horizontal scrolling doesn’t work.

Stretch columns to fit the grid width

Setting the grid’s width doesn’t change the width of your columns. When the columns are narrower than the grid, the space on the right stays empty. To redistribute the column widths so they fill the grid’s width, use the stretchH option: 'all' stretches all columns proportionally, and 'last' stretches only the last column.

For live examples of both modes, see the column stretching section of the Column width guide.

Autoresizing

Handsontable observes window resizing. If the window’s dimensions have changed, then we check if Handsontable should resize itself too. Due to the performance issue, we use the debounce method to respond on window resize.

You can easily overwrite this behavior by returning false in the beforeRefreshDimensions hook.

<HotTable beforeRefreshDimensions={() => false} />

Manual resizing

The Handsontable instance exposes the refreshDimensions() method, which helps you to resize grid elements properly.

hot.refreshDimensions();

You can listen for two hooks, beforeRefreshDimensions and afterRefreshDimensions.

JavaScript
import { useRef, useState, useEffect } from 'react';
import { HotTable } from '@handsontable/react-wrapper';
import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modules
registerAllModules();
// generate an array of arrays with dummy data
const data = new Array(100) // number of rows
.fill(null)
.map((_, row) =>
new Array(50) // number of columns
.fill(null)
.map((_, column) => `${row}, ${column}`)
);
const ExampleComponent = () => {
const [isContainerExpanded, setIsContainerExpanded] = useState(false);
const hotRef = useRef(null);
const triggerBtnClickCallback = () => {
setIsContainerExpanded(!isContainerExpanded);
};
useEffect(() => {
// simulate layout change outside of React lifecycle
// @ts-ignore
document.getElementById('exampleParent').style.height = isContainerExpanded ? '410px' : '157px';
hotRef.current?.hotInstance?.refreshDimensions();
});
return (
<>
<div className="example-controls-container">
<div className="controls">
<button id="triggerBtn" className="button button--primary" onClick={() => triggerBtnClickCallback()}>
{isContainerExpanded ? 'Collapse container' : 'Expand container'}
</button>
</div>
</div>
<div id="exampleParent" className="exampleParent">
<HotTable
data={data}
rowHeaders={true}
colHeaders={true}
width="100%"
height="100%"
colWidths={100}
autoWrapRow={true}
autoWrapCol={true}
licenseKey="non-commercial-and-evaluation"
ref={hotRef}
/>
</div>
</>
);
};
export default ExampleComponent;
TypeScript
import { useRef, useState, useEffect } from 'react';
import { HotTable, HotTableRef } from '@handsontable/react-wrapper';
import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modules
registerAllModules();
// generate an array of arrays with dummy data
const data = new Array(100) // number of rows
.fill(null)
.map((_, row) =>
new Array(50) // number of columns
.fill(null)
.map((_, column) => `${row}, ${column}`)
);
const ExampleComponent = () => {
const [isContainerExpanded, setIsContainerExpanded] = useState(false);
const hotRef = useRef<HotTableRef>(null);
const triggerBtnClickCallback = () => {
setIsContainerExpanded(!isContainerExpanded);
};
useEffect(() => {
// simulate layout change outside of React lifecycle
// @ts-ignore
document.getElementById('exampleParent').style.height = isContainerExpanded ? '410px' : '157px';
hotRef.current?.hotInstance?.refreshDimensions();
});
return (
<>
<div className="example-controls-container">
<div className="controls">
<button id="triggerBtn" className="button button--primary" onClick={() => triggerBtnClickCallback()}>
{isContainerExpanded ? 'Collapse container' : 'Expand container'}
</button>
</div>
</div>
<div id="exampleParent" className="exampleParent">
<HotTable
data={data}
rowHeaders={true}
colHeaders={true}
width="100%"
height="100%"
colWidths={100}
autoWrapRow={true}
autoWrapCol={true}
licenseKey="non-commercial-and-evaluation"
ref={hotRef}
/>
</div>
</>
);
};
export default ExampleComponent;
CSS
#exampleParent {
height: 157px;
}
#exampleParent > div{
height: 100%;
}

Known limitations

Handsontable relies on the browser’s native scrollbars. Browsers cap how tall (or wide) a scrollable area can be, measured in CSS pixels. The taller the scroll area grows past that cap, the more rendering glitches appear - rows become misaligned, the autofill handle turns blurry, and eventually cell borders disappear.

The point where these glitches start depends on the browser, the operating system, and the device. The following approximate values were measured on macOS, and mark where problems begin rather than a hard cutoff:

BrowserGlitches start around
Chrome~8,000,000 px
Firefox~3,500,000 px
Safari~16,000,000 px

These values are approximate, were measured on specific browser versions, and can change as browsers update.

To estimate the maximum number of rows, divide the browser’s pixel limit by your row height. With the default row height of 23 px, Chrome stays reliable up to about 350,000 rows (8,000,000 / 23). To estimate the maximum number of columns, divide the pixel limit by your column width. With a column width of 50 px, that’s about 160,000 columns (8,000,000 / 50).

Taller rows or wider columns lower these limits proportionally. For example, with a row height of 100 px, Chrome’s limit drops to about 80,000 rows (8,000,000 / 100).

If your dataset can grow past these limits, load it in smaller chunks, for example with server-side or lazy data loading.

Frozen rows and columns carry a separate limit: the frozen area must fit within the grid’s width and height. Handsontable always draws that area in full, so when it needs more room than the grid has, it covers the whole grid and the rest can no longer be scrolled into view. Read more in Column freezing and Row freezing.

Result

Your grid now renders at the dimensions you specified, responding to container size or fixed pixel values as configured.

Related guides

Configuration options

Core methods

Hooks