Formatting cells
Change the appearance of cells, using custom CSS classes, inline styles, or custom cell borders.
Overview
Handsontable renders an HTML table, so you can style existing tr and td elements or attach your own classes.
Choose an approach based on your goal:
- Use
classNamewhen you want reusable static styles. - Use
rendererwhen you need to apply inline styles to specific cells at render time. - Use
customBorderswhen you need custom border widths, colors, or styles for selected ranges.
Prerequisites
- A Handsontable instance with data loaded.
- A stylesheet, if you format cells through custom CSS classes.
Apply custom CSS class styles
In this example, you add a custom class custom-cell to the cell in the top-left corner and a custom-table CSS class that highlights the table headers.
To add a CSS class to a cell, column, or row, use the className option. Set it in the grid configuration, in a columns entry, or per cell through the cells callback. The class you provide is added to the cell’s <td> element, where your CSS rules can target it.
import { HotTable } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modulesregisterAllModules();
const ExampleComponent = () => { return ( <HotTable data={[ ['SKU-4821', 'Laptop Pro 15', 'Electronics', 149900, 42], ['SKU-0093', 'Wireless Mouse', 'Peripherals', 2999, 218], ['SKU-7712', 'USB-C Hub 7-port', 'Peripherals', 5499, 0], ['SKU-3305', 'Mech. Keyboard', 'Peripherals', 8999, 67], ['SKU-9140', '4K Monitor 27"', 'Electronics', 34999, 15], ]} rowHeaders={true} colHeaders={['SKU', 'Product', 'Category', 'Price ($)', 'Stock']} stretchH="all" className="custom-table" cell={[ { row: 0, col: 0, className: 'custom-cell', }, ]} 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={[ ['SKU-4821', 'Laptop Pro 15', 'Electronics', 149900, 42], ['SKU-0093', 'Wireless Mouse', 'Peripherals', 2999, 218], ['SKU-7712', 'USB-C Hub 7-port', 'Peripherals', 5499, 0], ['SKU-3305', 'Mech. Keyboard', 'Peripherals', 8999, 67], ['SKU-9140', '4K Monitor 27"', 'Electronics', 34999, 15], ]} rowHeaders={true} colHeaders={['SKU', 'Product', 'Category', 'Price ($)', 'Stock']} stretchH="all" className="custom-table" cell={[ { row: 0, col: 0, className: 'custom-cell', }, ]} height="auto" autoWrapRow={true} autoWrapCol={true} licenseKey="non-commercial-and-evaluation" /> );};
export default ExampleComponent;td.custom-cell { color: #fff !important; background-color: #254ac6 !important;}.custom-table thead th:nth-child(even),.custom-table tbody tr:nth-child(odd) th { color: #fff !important; background-color: #254ac6 !important;}Give your rule enough specificity
A theme styles cells through rules such as .ht-theme-main .htCore td, which is more specific than a single class selector. A rule like .custom-cell { font-size: 24px; } therefore loses to the theme, and the cell keeps the theme’s font size.
To win, include the theme class and the element in your selector:
.ht-theme-main .htCore td.custom-cell { font-size: 24px;}Adding !important to your original rule works too, but the selector above keeps the cascade readable.
autoRowSize and autoColumnSize measure each cell with its class applied, so a class that changes a cell’s font size, padding, or borders is reflected in the calculated row heights and column widths.
Apply inline styles
Apply inline styles directly to a cell’s DOM element through the style property. Use the renderer option to run this logic on each render.
import { HotTable } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';import { textRenderer, registerRenderer } from 'handsontable/renderers';
// register Handsontable's modulesregisterAllModules();
const ExampleComponent = () => { registerRenderer('customStylesRenderer', (hotInstance, TD, ...rest) => { textRenderer(hotInstance, TD, ...rest); TD.style.fontWeight = 'bold'; TD.style.color = 'green'; TD.style.background = '#d7f1e1'; });
return ( <HotTable data={[ ['SKU-4821', 'Laptop Pro 15', 'Electronics', 149900, 42], ['SKU-0093', 'Wireless Mouse', 'Peripherals', 2999, 218], ['SKU-7712', 'USB-C Hub 7-port', 'Peripherals', 5499, 0], ['SKU-3305', 'Mech. Keyboard', 'Peripherals', 8999, 67], ['SKU-9140', '4K Monitor 27"', 'Electronics', 34999, 15], ]} rowHeaders={true} colHeaders={['SKU', 'Product', 'Category', 'Price ($)', 'Stock']} stretchH="all" cell={[ { row: 0, col: 0, renderer: 'customStylesRenderer', }, ]} 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';import { textRenderer, registerRenderer } from 'handsontable/renderers';
// register Handsontable's modulesregisterAllModules();
const ExampleComponent = () => { registerRenderer('customStylesRenderer', (hotInstance, TD, ...rest) => { textRenderer(hotInstance, TD, ...rest);
TD.style.fontWeight = 'bold'; TD.style.color = 'green'; TD.style.background = '#d7f1e1'; });
return ( <HotTable data={[ ['SKU-4821', 'Laptop Pro 15', 'Electronics', 149900, 42], ['SKU-0093', 'Wireless Mouse', 'Peripherals', 2999, 218], ['SKU-7712', 'USB-C Hub 7-port', 'Peripherals', 5499, 0], ['SKU-3305', 'Mech. Keyboard', 'Peripherals', 8999, 67], ['SKU-9140', '4K Monitor 27"', 'Electronics', 34999, 15], ]} rowHeaders={true} colHeaders={['SKU', 'Product', 'Category', 'Price ($)', 'Stock']} stretchH="all" cell={[ { row: 0, col: 0, renderer: 'customStylesRenderer', }, ]} height="auto" autoWrapRow={true} autoWrapCol={true} licenseKey="non-commercial-and-evaluation" /> );};
export default ExampleComponent;Custom cell borders
To enable custom borders, set customBorders. You can set it to true or pass an array with predefined border configuration. For all settings and methods, see the API reference.
In API property names, start and end refer to the starting and ending edges of the layout direction.
You can customize the border style using the style property in the border configuration. The available options are:
'solid'(default) - A solid line border'dashed'- A dashed line border'dotted'- A dotted line border
The style property can be set for any border edge (top, bottom, start, end). When not specified, it defaults to 'solid'.
The following example shows different border styles on selected cell ranges.
import { HotTable } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modulesregisterAllModules();
const ExampleComponent = () => { return ( <HotTable data={[ ['SKU-4821', 'Laptop Pro 15', 'Electronics', 149900, 42], ['SKU-0093', 'Wireless Mouse', 'Peripherals', 2999, 218], ['SKU-7712', 'USB-C Hub 7-port', 'Peripherals', 5499, 0], ['SKU-3305', 'Mech. Keyboard', 'Peripherals', 8999, 67], ['SKU-9140', '4K Monitor 27"', 'Electronics', 34999, 15], ]} rowHeaders={true} colHeaders={['SKU', 'Product', 'Category', 'Price ($)', 'Stock']} stretchH="all" height="auto" autoWrapRow={true} autoWrapCol={true} licenseKey="non-commercial-and-evaluation" customBorders={[ { range: { from: { row: 1, col: 1, }, to: { row: 3, col: 4, }, }, top: { width: 2, color: '#5292F7', style: 'dotted', }, bottom: { width: 2, color: 'red', }, start: { width: 2, color: 'orange', style: 'dashed', }, end: { width: 2, color: 'magenta', }, }, { row: 2, col: 2, start: { width: 2, color: 'red', }, end: { width: 1, color: 'green', }, }, ]} /> );};
export default ExampleComponent;import { HotTable } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modulesregisterAllModules();
const ExampleComponent = () => { return ( <HotTable data={[ ['SKU-4821', 'Laptop Pro 15', 'Electronics', 149900, 42], ['SKU-0093', 'Wireless Mouse', 'Peripherals', 2999, 218], ['SKU-7712', 'USB-C Hub 7-port', 'Peripherals', 5499, 0], ['SKU-3305', 'Mech. Keyboard', 'Peripherals', 8999, 67], ['SKU-9140', '4K Monitor 27"', 'Electronics', 34999, 15], ]} rowHeaders={true} colHeaders={['SKU', 'Product', 'Category', 'Price ($)', 'Stock']} stretchH="all" height="auto" autoWrapRow={true} autoWrapCol={true} licenseKey="non-commercial-and-evaluation" customBorders={[ { range: { from: { row: 1, col: 1, }, to: { row: 3, col: 4, }, }, top: { width: 2, color: '#5292F7', style: 'dotted', }, bottom: { width: 2, color: 'red', }, start: { width: 2, color: 'orange', style: 'dashed', }, end: { width: 2, color: 'magenta', }, }, { row: 2, col: 2, start: { width: 2, color: 'red', }, end: { width: 1, color: 'green', }, }, ]} /> );};
export default ExampleComponent;Apply borders progressively for large configurations
Building a very large customBorders configuration before the first render can delay the initial paint. To render the grid first and apply the borders in background batches, set customBordersProgressive to true:
const hot = new Handsontable(container, { data, customBorders: largeBorderConfig, customBordersProgressive: true, licenseKey: 'non-commercial-and-evaluation',});Pass an object to control how many border entries are applied per batch:
customBordersProgressive: { chunkSize: 5000 },With progressive application enabled, the borders fill in after the grid becomes interactive, so getBorders() and the cells’ border metadata are complete only once the afterCustomBordersUpdate hook fires:
const hot = new Handsontable(container, { data, customBorders: largeBorderConfig, customBordersProgressive: true, licenseKey: 'non-commercial-and-evaluation', afterCustomBordersUpdate() { // every custom border is now applied },});Result
The grid renders your configured classes, inline styles, and border definitions. Formatting stays consistent after each render.
Related articles
Related guides
Configuration options
Plugins