- [View related topics](https://github.com/handsontable/handsontable/labels/Column%20sorting) on GitHub
- [Report an issue](https://github.com/handsontable/handsontable/issues/new/choose) on GitHub
- [Start a discussion](https://forum.handsontable.com/c/getting-help/questions) on Handsontable's forum
- [Contact our technical support](https://handsontable.com/contact?category=technical_support) to get help
---
# Rows pagination
URL: https://handsontable.com/docs/javascript-data-grid/rows-pagination/
Added in Handsontable 16.1.0.
Split large data sets into pages to improve usability and rendering performance. Pagination operates fully on the client side and automatically recomputes the total page count whenever rows are added, removed, filtered, or otherwise modified.
[[toc]]
## Pagination demo
Use the controls below the grid to switch between pages.
::: only-for javascript
::: example #example1 --js 1 --ts 2
@[code collapse={8-107, 117-162}](@/content/guides/rows/rows-pagination/javascript/example1.js)
@[code collapse={8-107, 117-162}](@/content/guides/rows/rows-pagination/javascript/example1.ts)
:::
:::
::: only-for react
::: example #example1 :react --js 1 --ts 2
@[code collapse={8-107, 126-156}](@/content/guides/rows/rows-pagination/react/example1.jsx)
@[code collapse={8-107, 126-156}](@/content/guides/rows/rows-pagination/react/example1.tsx)
:::
:::
::: only-for angular
::: example #example1 :angular --ts 1 --html 2
@[code collapse={16-115, 122-168}](@/content/guides/rows/rows-pagination/angular/example1.ts)
@[code](@/content/guides/rows/rows-pagination/angular/example1.html)
:::
:::
::: only-for vue
::: example #example1 :vue3
@[code](@/content/guides/rows/rows-pagination/vue/example1.vue)
:::
:::
## Enable pagination
To enable pagination set the [`pagination`](@/api/options.md#pagination) option to `true`.
```javascript
const configurationOptions = {
pagination: true,
};
```
Defining the option as `true` is equivalent to defining the following options:
```javascript
const configurationOptions = {
pagination: {
pageSize: 10,
pageSizeList: ['auto', 5, 10, 20, 50, 100],
initialPage: 1,
showPageSize: true,
showCounter: true,
showNavigation: true,
uiContainer: null,
},
};
```
## Configure pagination
You can customize pagination using available settings, such as showing or hiding specific UI sections like the page size selector, page counter, or page navigation.
Control aspects such as the number of rows per page, the initial page on load, or whether the grid should automatically adjust page size based on the container's height.
You can configure the following options:
```javascript
const configurationOptions = {
pagination: {
// Set number of rows per page. If the value is `auto` then the
// page size is calculated
// based on available height.
pageSize: 20,
// Provide a list of selectable page sizes
pageSizeList: ['auto', 10, 20, 50],
// Set the initial page when the grid loads
initialPage: 2,
// Show or hide the page size section
showPageSize: false,
// Show or hide the page counter section
showCounter: true,
// Show or hide the page navigation section
showNavigation: true,
// Custom container where the pagination UI will be injected
// (optional)
uiContainer: null,
}
};
```
In the data grid below, several pagination options are applied to provide a customized experience. The demo allows you to resize the table container and observe how the auto page size feature (`pageSize: 'auto'`) dynamically adjusts the number of visible rows to fit the available space.
::: only-for javascript
::: example #example2 --html 1 --js 2 --ts 3
@[code](@/content/guides/rows/rows-pagination/javascript/example2.html)
@[code collapse={8-107, 124-169}](@/content/guides/rows/rows-pagination/javascript/example2.js)
@[code collapse={8-107, 124-169}](@/content/guides/rows/rows-pagination/javascript/example2.ts)
:::
:::
::: only-for react
::: example #example2 :react --js 1 --css 2 --ts 3
@[code collapse={8-107, 137-167}](@/content/guides/rows/rows-pagination/react/example2.jsx)
@[code](@/content/guides/rows/rows-pagination/react/example2.css)
@[code collapse={8-107, 137-167}](@/content/guides/rows/rows-pagination/react/example2.tsx)
:::
:::
::: only-for angular
::: example #example2 :angular --ts 1 --html 2
@[code collapse={16-115, 142-188}](@/content/guides/rows/rows-pagination/angular/example2.ts)
@[code](@/content/guides/rows/rows-pagination/angular/example2.html)
:::
:::
::: only-for vue
::: example #example2 :vue3
@[code](@/content/guides/rows/rows-pagination/vue/example2.vue)
:::
:::
## Control pagination programmatically
Build your own pagination UI using API methods such as [`setPage()`](@/api/pagination.md#setpage), [`nextPage()`](@/api/pagination.md#nextpage), [`prevPage()`](@/api/pagination.md#prevpage), and more. For a complete list of available methods and hooks, see the [`Pagination`](@/api/pagination.md) plugin API reference.
::: only-for javascript
::: example #example3 --html 1 --js 2 --ts 3
@[code](@/content/guides/rows/rows-pagination/javascript/example3.html)
@[code collapse={8-107, 122-167}](@/content/guides/rows/rows-pagination/javascript/example3.js)
@[code collapse={8-107, 123-168}](@/content/guides/rows/rows-pagination/javascript/example3.ts)
:::
:::
::: only-for react
::: example #example3 :react --js 1 --css 2 --ts 3
@[code collapse={8-107, 200-230}](@/content/guides/rows/rows-pagination/react/example3.jsx)
@[code](@/content/guides/rows/rows-pagination/react/example3.css)
@[code collapse={8-107, 200-230}](@/content/guides/rows/rows-pagination/react/example3.tsx)
:::
:::
::: only-for angular
::: example #example3 :angular --ts 1 --html 2
@[code collapse={42-141, 154-200}](@/content/guides/rows/rows-pagination/angular/example3.ts)
@[code](@/content/guides/rows/rows-pagination/angular/example3.html)
:::
:::
::: only-for vue
::: example #example3 :vue3
@[code](@/content/guides/rows/rows-pagination/vue/example3.vue)
:::
:::
## Choose where to display the pagination UI
By default, the pagination UI is displayed at the bottom of the grid. You can change the place of the pagination component by setting the [`uiContainer`](@/api/options.md#uicontainer) option to a custom container element.
::: only-for javascript
::: example #example4 --html 1 --js 2 --ts 3
@[code](@/content/guides/rows/rows-pagination/javascript/example4.html)
@[code collapse={8-107, 119-164}](@/content/guides/rows/rows-pagination/javascript/example4.js)
@[code collapse={8-107, 119-164}](@/content/guides/rows/rows-pagination/javascript/example4.ts)
:::
:::
::: only-for react
::: example #example4 :react --js 1 --css 2 --ts 3
@[code collapse={8-107, 140-170}](@/content/guides/rows/rows-pagination/react/example4.jsx)
@[code](@/content/guides/rows/rows-pagination/react/example4.css)
@[code collapse={8-107, 140-170}](@/content/guides/rows/rows-pagination/react/example4.tsx)
:::
:::
::: only-for angular
::: example #example4 :angular --ts 1 --html 2
@[code collapse={16-115, 144-190}](@/content/guides/rows/rows-pagination/angular/example4.ts)
@[code](@/content/guides/rows/rows-pagination/angular/example4.html)
:::
:::
::: only-for vue
::: example #example4 :vue3
@[code](@/content/guides/rows/rows-pagination/vue/example4.vue)
:::
:::
## Modify paged data
Sometimes you need to modify data only on the currently visible page. Core method like [`setDataAtCell`](@/api/core.md#setdataatcell) operates on all rows, including those hidden by pagination. To modify data only on the current page, you can use the [`getPaginationData`](@/api/pagination.md#getpaginationdata) method to get the pagination state and use it in conjunction with Core method.
::: only-for javascript
::: example #example5 --html 1 --js 2 --ts 3
@[code](@/content/guides/rows/rows-pagination/javascript/example5.html)
@[code collapse={8-107, 116-161}](@/content/guides/rows/rows-pagination/javascript/example5.js)
@[code collapse={8-107, 117-162}](@/content/guides/rows/rows-pagination/javascript/example5.ts)
:::
:::
::: only-for react
::: example #example5 :react --js 1 --css 2 --ts 3
@[code collapse={8-107, 158-188}](@/content/guides/rows/rows-pagination/react/example5.jsx)
@[code](@/content/guides/rows/rows-pagination/react/example5.css)
@[code collapse={8-107, 159-189}](@/content/guides/rows/rows-pagination/react/example5.tsx)
:::
:::
::: only-for angular
::: example #example5 :angular --ts 1 --html 2
@[code collapse={25-124, 131-177}](@/content/guides/rows/rows-pagination/angular/example5.ts)
@[code](@/content/guides/rows/rows-pagination/angular/example5.html)
:::
:::
::: only-for vue
::: example #example5 :vue3
@[code](@/content/guides/rows/rows-pagination/vue/example5.vue)
:::
:::
## Use pagination hooks
You can run your code before or after different stages of pagination, using the following [Handsontable hooks](@/guides/getting-started/events-and-hooks/events-and-hooks.md):
- [`beforePageChange()`](@/api/hooks.md#beforepagechange)
- [`afterPageChange()`](@/api/hooks.md#afterpagechange)
- [`beforePageSizeChange()`](@/api/hooks.md#beforepagesizechange)
- [`afterPageSizeChange()`](@/api/hooks.md#afterpagesizechange)
- [`afterPageSizeVisibilityChange()`](@/api/hooks.md#afterpagesizevisibilitychange)
- [`afterPageCounterVisibilityChange()`](@/api/hooks.md#afterpagecountervisibilitychange)
- [`afterPageNavigationVisibilityChange()`](@/api/hooks.md#afterpagenavigationvisibilitychange)
::: only-for javascript
```js
const configurationOptions = {
beforePageChange() {
// add your code here
return false; // to block page change
},
afterPageChange() {
// add your code here
},
beforePageSizeChange() {
// add your code here
return false; // to block page size change
},
// ...
};
```
:::
::: only-for react
```jsx
{
// add your code here
return false; // to block page change
}}
afterPageChange={() => {
// add your code here
}}
beforePageSizeChange={() => {
// add your code here
return false; // to block page size change
}}
// ...
/>
```
:::
::: only-for vue
```ts
const hotSettings = {
beforePageChange() {
// add your code here
return false; // to block page change
},
afterPageChange() {
// add your code here
},
beforePageSizeChange() {
// add your code here
return false; // to block page size change
},
// ...
};
```
:::
## Localize pagination
Translate default pagination labels - such as "Page size:", "Page" and more - using the global translations mechanism. The pagination introduces the following keys to the language dictionary that you can use to translate the pagination UI:
- `PAGINATION_SECTION = 'Pagination'`
- `PAGINATION_PAGE_SIZE_SECTION = 'Page size'`
- `PAGINATION_PAGE_SIZE_AUTO = 'Auto'`
- `PAGINATION_COUNTER_SECTION = '[start] - [end] of [total]'`
- `PAGINATION_NAV_SECTION = 'Page [currentPage] of [totalPages]'`
- `PAGINATION_FIRST_PAGE = 'Go to first page'`
- `PAGINATION_PREV_PAGE = 'Go to previous page'`
- `PAGINATION_NEXT_PAGE = 'Go to next page'`
- `PAGINATION_LAST_PAGE = 'Go to last page'`
To learn more about the translation mechanism, see the [Languages guide](@/guides/internationalization/language/language.md).
The example below demonstrates how to customize the translation of the pagination counter and navigation sections.
::: only-for javascript
::: example #example6 --js 1 --ts 2
@[code collapse={8-107, 129-174}](@/content/guides/rows/rows-pagination/javascript/example6.js)
@[code collapse={8-107, 129-174}](@/content/guides/rows/rows-pagination/javascript/example6.ts)
:::
:::
::: only-for react
::: example #example6 :react --js 1 --ts 2
@[code collapse={8-107, 139-169}](@/content/guides/rows/rows-pagination/react/example6.jsx)
@[code collapse={8-107, 139-169}](@/content/guides/rows/rows-pagination/react/example6.tsx)
:::
:::
::: only-for angular
::: example #example6 :angular --ts 1 --html 2
@[code collapse={16-115, 139-185}](@/content/guides/rows/rows-pagination/angular/example6.ts)
@[code](@/content/guides/rows/rows-pagination/angular/example6.html)
:::
:::
::: only-for vue
::: example #example6 :vue3
@[code](@/content/guides/rows/rows-pagination/vue/example6.vue)
:::
:::
## Customize pagination UI
You can customize the look of each pagination element by assigning new values to the CSS variables defined in our main, horizon and classic themes, aligning them with your app's design guidelines. For a list of available variables, see the [Handsontable Design System](@/guides/styling/design-system/design-system.md) on Figma.
Within the plugin the following CSS variables are available:
- `--ht-pagination-bar-foreground-color`: Controls the text color of pagination bar elements;
- `--ht-pagination-bar-background-color`: Sets the background color of the pagination bar;
- `--ht-pagination-bar-horizontal-padding`: Defines the left and right padding of the pagination bar;
- `--ht-pagination-bar-vertical-padding`: Controls the top and bottom padding of the pagination bar;
## Importing the pagination module
You can reduce the size of your bundle by importing and using only the [modules](@/guides/tools-and-building/modules/modules.md) that you need.
To use pagination, you need only the following modules:
- The [base module](@/guides/tools-and-building/modules/modules.md#import-the-base-module)
- The [`Pagination`](@/api/pagination.md) module
```js
// import the base module
import Handsontable from 'handsontable/base';
// import the Pagination plugin
import { registerPlugin, Pagination } from 'handsontable/plugins';
// register the Pagination plugin
registerPlugin(Pagination);
```
## Known limitations
When pagination is enabled:
- [`fixedRowsTop`](@/api/options.md#fixedrowstop) and [`fixedRowsBottom`](@/api/options.md#fixedrowsbottom) options should be disabled.
- [`NestedRows`](@/api/nestedRows.md) and [`MergeCells`](@/api/mergeCells.md) plugins should be disabled.
- The [`height`](@/api/options.md#height) option set as `auto` is not supported when the `pageSize: 'auto'` is set.
- Pagination always displays a fixed number of rows per page (default is `10`), regardless of data changes such as hiding, trimming, filtering, removing, adding, or pasting rows - unless `pageSize: 'auto'` is set.
## Result
After completing this guide, your grid divides rows into pages with built-in navigation controls. You can configure the page size, customize the UI position, control pagination programmatically, and react to page changes using hooks.
## Related blog articles
- [Handsontable 16.1: Row Pagination, Loading Plugin, and Long-Term Support Policy](https://handsontable.com/blog/handsontable-16.1-row-pagination-loading-plugin-and-long-term-support-policy)
## Related API reference
**Configuration options**
- [pagination](@/api/options.md#pagination)
- [fixedRowsBottom](@/api/options.md#fixedrowsbottom)
- [fixedRowsTop](@/api/options.md#fixedrowstop)
**Hooks**
- [beforePageChange()](@/api/hooks.md#beforepagechange)
- [afterPageChange()](@/api/hooks.md#afterpagechange)
- [beforePageSizeChange()](@/api/hooks.md#beforepagesizechange)
- [afterPageSizeChange()](@/api/hooks.md#afterpagesizechange)
- [afterPageSizeVisibilityChange()](@/api/hooks.md#afterpagesizevisibilitychange)
- [afterPageCounterVisibilityChange()](@/api/hooks.md#afterpagecountervisibilitychange)
- [afterPageNavigationVisibilityChange()](@/api/hooks.md#afterpagenavigationvisibilitychange)
**Plugins**
- [Pagination](@/api/pagination.md)
- [NestedRows](@/api/nestedRows.md)
- [MergeCells](@/api/mergeCells.md)
## Troubleshooting
Didn't find what you need? Try this:
- [View related topics](https://github.com/handsontable/handsontable/labels/Pagination) on GitHub
- [Report an issue](https://github.com/handsontable/handsontable/issues/new/choose) on GitHub
- [Start a discussion](https://forum.handsontable.com/c/getting-help/questions) on Handsontable's forum
- [Contact our technical support](https://handsontable.com/contact?category=technical_support) to get help
---
# Row trimming
URL: https://handsontable.com/docs/javascript-data-grid/row-trimming/
Hide individual rows from your interface and exclude them from the rendering process and `DataMap`.
[[toc]]
## Overview
Row trimming is similar to [row hiding](@/guides/rows/row-hiding/row-hiding.md), but works a bit differently.
The [`TrimRows`](@/api/trimRows.md) plugin allows the trimming of specific rows from the table. Rows being trimmed aren't rendered and aren't included in the `DataMap`, which can be retrieved by calling the [`getData`](@/api/core.md#getdata) method.
Unlike hidden rows, trimmed rows are removed from the visual dataset entirely, so rows after them shift to a lower visual index. For a full comparison, see [Understanding data and indexes](@/guides/getting-started/understanding-data-and-indexes/understanding-data-and-indexes.md#how-features-affect-the-two-datasets).
## Setup
To enable row trimming, set the [`trimRows`](@/api/options.md#trimrows) option to `true`.
::: only-for javascript
```js
// enable the `TrimRows` plugin
trimRows: true,
```
:::
::: only-for react
```jsx
// enable the `TrimRows` plugin
```
:::
::: only-for angular
```ts
// enable the `TrimRows` plugin
trimRows: true,
```
:::
::: only-for vue
```ts
// enable the `TrimRows` plugin
trimRows: true,
```
:::
To both enable row trimming and trim selected rows at Handsontable's initialization, set the [`trimRows`](@/api/options.md#trimrows) option to an array of physical row indexes.
::: only-for javascript
```js
// enable the `TrimRows` plugin
// at Handsontable's initialization, trim rows 5, 10, and 15
trimRows: [5, 10, 15],
```
:::
::: only-for react
```jsx
// enable the `TrimRows` plugin
// at Handsontable's initialization, trim rows 5, 10, and 15
```
:::
::: only-for angular
```ts
// enable the `TrimRows` plugin
// at Handsontable's initialization, trim rows 5, 10, and 15
trimRows: [5, 10, 15],
```
:::
::: only-for vue
```ts
// enable the `TrimRows` plugin
// at Handsontable's initialization, trim rows 5, 10, and 15
trimRows: [5, 10, 15],
```
:::
## Example
Note that the second, third, and sixth rows are missing in the following example:
::: only-for javascript
::: example #example1 --js 1 --ts 2
@[code](@/content/guides/rows/row-trimming/javascript/example1.js)
@[code](@/content/guides/rows/row-trimming/javascript/example1.ts)
:::
:::
::: only-for react
::: example #example1 :react --js 1 --ts 2
@[code](@/content/guides/rows/row-trimming/react/example1.jsx)
@[code](@/content/guides/rows/row-trimming/react/example1.tsx)
:::
:::
::: only-for angular
::: example #example1 :angular --ts 1 --html 2
@[code](@/content/guides/rows/row-trimming/angular/example1.ts)
@[code](@/content/guides/rows/row-trimming/angular/example1.html)
:::
:::
::: only-for vue
::: example #example1 :vue3
@[code](@/content/guides/rows/row-trimming/vue/example1.vue)
:::
:::
## Result
After completing this guide, rows you specify are excluded from rendering and from `DataMap`. You can trim rows at initialization or dynamically at runtime using the plugin API.
## Related API
### Plugin methods
::: only-for react
::: tip
To use the Handsontable API, you'll need access to the Handsontable instance. You can do that by utilizing a reference to the `HotTable` component, and reading its `hotIntance` property.
For more information, see the [Instance methods](@/guides/getting-started/react-methods/react-methods.md) page.
:::
:::
::: only-for angular
To obtain the Handsontable instance from the Angular wrapper component, you can use Angular's `@ViewChild` decorator to reference the `HotTableComponent` and then access its `hotInstance` property.
```ts
@ViewChild(HotTableComponent, {static: false})
hotTable!: HotTableComponent;
ngAfterViewInit() {
// The Handsontable instance is available through the hotInstance property
const hot = this.hotTable.hotInstance;
// You can now use the "hot" instance to call Handsontable's API methods
// ...
}
```
:::
::: only-for vue
::: tip
To use the Handsontable API, you'll need access to the Handsontable instance. You can do that by utilizing a reference to the `HotTable` component, and reading its `hotInstance` property.
For more information, see the [Instance methods](@/guides/getting-started/vue3-hot-reference/vue3-hot-reference.md) page.
:::
:::
The plugin instance can be accessed by calling:
```js
const plugin = hot.getPlugin('trimRows');
```
To trim a single row, call the [`trimRow()`](@/api/trimRows.md#trimrow) method of the plugin object:
```js
plugin.trimRow(4);
```
To trim multiple rows, either pass them as arguments to the [`trimRow()`](@/api/trimRows.md#trimrow) method, or pass an array of physical row indexes to the [`trimRows()`](@/api/trimRows.md#trimrows) method:
```js
plugin.trimRow(0, 4, 6);
// or
plugin.trimRows([0, 4, 6]);
```
To restore the trimmed row(s), use the following methods:
```js
plugin.untrimRow(4);
```
```js
plugin.untrimRow(0, 4, 6);
```
```js
plugin.untrimRows([0, 4, 6]);
```
To see the changes made, call `hot.render();` to re-render the table.
### API reference
**Options**
- [trimRows](@/api/options.md#trimrows)
**Hooks**
- [afterTrimRow](@/api/hooks.md#aftertrimrow)
- [afterUntrimRow](@/api/hooks.md#afteruntrimrow)
- [beforeTrimRow](@/api/hooks.md#beforetrimrow)
- [beforeUntrimRow](@/api/hooks.md#beforeuntrimrow)
**Plugins**
- [TrimRows](@/api/trimRows.md)
---
# Row pre-populating
URL: https://handsontable.com/docs/javascript-data-grid/row-prepopulating/
Pre-populate new rows with default values when users add rows to the grid.
[[toc]]
## Basic spare rows
To keep one empty row at the bottom of the grid, set [`minSpareRows`](@/api/options.md#minsparerows) to `1`.
::: only-for javascript
::: example #example1 --js 1 --ts 2
@[code](@/content/guides/rows/row-prepopulating/javascript/example1.js)
@[code](@/content/guides/rows/row-prepopulating/javascript/example1.ts)
:::
:::
::: only-for react
::: example #example1 :react --js 1 --ts 2
@[code](@/content/guides/rows/row-prepopulating/react/example1.jsx)
@[code](@/content/guides/rows/row-prepopulating/react/example1.tsx)
:::
:::
::: only-for angular
::: example #example1 :angular --ts 1 --html 2
@[code](@/content/guides/rows/row-prepopulating/angular/example1.ts)
@[code](@/content/guides/rows/row-prepopulating/angular/example1.html)
:::
:::
::: only-for vue
::: example #example1 :vue3
@[code](@/content/guides/rows/row-prepopulating/vue/example1.vue)
:::
:::
## Spare rows with placeholder styling
To hint what to enter in the spare row, add a custom cell renderer that displays greyed-out placeholder text in empty cells. The renderer checks whether the whole row is empty, then shows a template value in a lighter color.
::: only-for javascript
::: example #example2 --js 1 --ts 2
@[code](@/content/guides/rows/row-prepopulating/javascript/example2.js)
@[code](@/content/guides/rows/row-prepopulating/javascript/example2.ts)
:::
:::
::: only-for react
::: example #example2 :react --js 1 --ts 2
@[code](@/content/guides/rows/row-prepopulating/react/example2.jsx)
@[code](@/content/guides/rows/row-prepopulating/react/example2.tsx)
:::
:::
::: only-for angular
::: example #example2 :angular --ts 1 --html 2
@[code](@/content/guides/rows/row-prepopulating/angular/example2.ts)
@[code](@/content/guides/rows/row-prepopulating/angular/example2.html)
:::
:::
::: only-for vue
::: example #example2 :vue3
@[code](@/content/guides/rows/row-prepopulating/vue/example2.vue)
:::
:::
## Auto-populating with template values
For full pre-population, use the [`beforeChange`](@/api/hooks.md#beforechange) hook to fill all cells in a spare row with template values the moment the user starts editing. The `isEmptyRow()` helper detects whether the row is untouched, and the hook pushes changes for every column except the one the user is editing.
::: only-for javascript
::: example #example3 --js 1 --ts 2
@[code](@/content/guides/rows/row-prepopulating/javascript/example3.js)
@[code](@/content/guides/rows/row-prepopulating/javascript/example3.ts)
:::
:::
::: only-for react
::: example #example3 :react --js 1 --ts 2
@[code](@/content/guides/rows/row-prepopulating/react/example3.jsx)
@[code](@/content/guides/rows/row-prepopulating/react/example3.tsx)
:::
:::
::: only-for angular
::: example #example3 :angular --ts 1 --html 2
@[code](@/content/guides/rows/row-prepopulating/angular/example3.ts)
@[code](@/content/guides/rows/row-prepopulating/angular/example3.html)
:::
:::
::: only-for vue
::: example #example3 :vue3
@[code](@/content/guides/rows/row-prepopulating/vue/example3.vue)
:::
:::
## Pre-populate from an adjacent row
You can copy values from the row above when the user inserts a new row. Use the [`afterCreateRow`](@/api/hooks.md#aftercreaterow) hook to read the source row's data and write it to the newly created row.
```js
const hot = new Handsontable(container, {
data: [
['Product A', 10, 'active'],
['Product B', 20, 'inactive'],
],
colHeaders: ['Name', 'Quantity', 'Status'],
afterCreateRow(index) {
if (index > 0) {
const sourceRow = hot.getSourceDataAtRow(index - 1);
sourceRow.forEach((value, col) => {
hot.setDataAtCell(index, col, value);
});
}
},
licenseKey: 'non-commercial-and-evaluation',
});
```
## Pre-populate from a server-fetched default
You can fetch default row values from a server and apply them when the user adds a new row. Use the [`afterCreateRow`](@/api/hooks.md#aftercreaterow) hook to trigger the request and populate the row once the response arrives.
```js
const hot = new Handsontable(container, {
data: [],
colHeaders: ['Name', 'Quantity', 'Status'],
afterCreateRow(index) {
fetch('/api/row-defaults')
.then((response) => response.json())
.then((defaults) => {
hot.setDataAtRow(index, [defaults.name, defaults.quantity, defaults.status]);
});
},
licenseKey: 'non-commercial-and-evaluation',
});
```
## Result
Your grid keeps one or more empty rows at the bottom. Depending on the approach, spare rows show greyed-out placeholder text or auto-fill all cells with template values when the user starts editing.
## Related API reference
**Hooks**
- [afterCreateRow](@/api/hooks.md#aftercreaterow)
- [beforeChange](@/api/hooks.md#beforechange)
---
# Selection
URL: https://handsontable.com/docs/javascript-data-grid/selection/
Select a single cell, a range of adjacent cells, or multiple non-adjacent ranges of cells.
[[toc]]
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`](@/api/options.md#colheaders) or [`rowHeaders`](@/api/options.md#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`](@/api/options.md#selectionmode):
- [`single`](@/api/options.md#selectionmode) - You can select a single cell.
- [`range`](@/api/options.md#selectionmode) - You can select multiple cells within a single rangeselected.
- [`multiple`](@/api/options.md#selectionmode) - Multiple non-contiguous ranges of cells can be selected.
::: only-for javascript
::: example #example1 --html 1 --js 2 --ts 3 --css 4
@[code](@/content/guides/cell-features/selection/javascript/example1.html)
@[code](@/content/guides/cell-features/selection/javascript/example1.js)
@[code](@/content/guides/cell-features/selection/javascript/example1.ts)
@[code](@/content/guides/cell-features/selection/javascript/example1.css)
:::
:::
::: only-for react
::: example #example1 :react --js 1 --ts 2
@[code](@/content/guides/cell-features/selection/react/example1.jsx)
@[code](@/content/guides/cell-features/selection/react/example1.tsx)
:::
:::
::: only-for angular
::: example #example1 :angular --ts 1 --html 2
@[code](@/content/guides/cell-features/selection/angular/example1.ts)
@[code](@/content/guides/cell-features/selection/angular/example1.html)
:::
:::
::: only-for vue
::: example #example1 :vue3
@[code](@/content/guides/cell-features/selection/vue/example1.vue)
:::
:::
## Get data from the selected ranges
To retrieve the selected cells as an array of arrays, you use the [`getSelected()`](@/api/core.md#getselected) or [`getSelectedRange()`](@/api/core.md#getselectedrange) methods.
::: only-for javascript
::: example #example2 --html 1 --js 2 --ts 3
@[code](@/content/guides/cell-features/selection/javascript/example2.html)
@[code](@/content/guides/cell-features/selection/javascript/example2.js)
@[code](@/content/guides/cell-features/selection/javascript/example2.ts)
:::
:::
::: only-for react
::: example #example2 :react --js 1 --ts 2
@[code](@/content/guides/cell-features/selection/react/example2.jsx)
@[code](@/content/guides/cell-features/selection/react/example2.tsx)
:::
:::
::: only-for angular
::: example #example2 :angular --ts 1 --html 2
@[code](@/content/guides/cell-features/selection/angular/example2.ts)
@[code](@/content/guides/cell-features/selection/angular/example2.html)
:::
:::
::: only-for vue
::: example #example2 :vue3
@[code](@/content/guides/cell-features/selection/vue/example2.vue)
:::
:::
## 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.
::: only-for javascript
::: example #example3 --html 1 --css 2 --js 3 --ts 4
@[code](@/content/guides/cell-features/selection/javascript/example3.html)
@[code](@/content/guides/cell-features/selection/javascript/example3.css)
@[code](@/content/guides/cell-features/selection/javascript/example3.js)
@[code](@/content/guides/cell-features/selection/javascript/example3.ts)
:::
:::
::: only-for react
::: example #example3 :react --css 1 --js 2 --ts 3
@[code](@/content/guides/cell-features/selection/react/example3.css)
@[code](@/content/guides/cell-features/selection/react/example3.jsx)
@[code](@/content/guides/cell-features/selection/react/example3.tsx)
:::
:::
::: only-for angular
::: example #example3 :angular --ts 1 --html 2
@[code](@/content/guides/cell-features/selection/angular/example3.ts)
@[code](@/content/guides/cell-features/selection/angular/example3.html)
:::
:::
::: only-for vue
::: example #example3 :vue3
@[code](@/content/guides/cell-features/selection/vue/example3.vue)
:::
:::
## 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.
::: only-for javascript
::: example #example4 --html 1 --css 2 --js 3 --ts 4
@[code](@/content/guides/cell-features/selection/javascript/example4.html)
@[code](@/content/guides/cell-features/selection/javascript/example4.css)
@[code](@/content/guides/cell-features/selection/javascript/example4.js)
@[code](@/content/guides/cell-features/selection/javascript/example4.ts)
:::
:::
::: only-for react
::: example #example4 :react --css 1 --js 2 --ts 3
@[code](@/content/guides/cell-features/selection/react/example4.css)
@[code](@/content/guides/cell-features/selection/react/example4.jsx)
@[code](@/content/guides/cell-features/selection/react/example4.tsx)
:::
:::
::: only-for angular
::: example #example4 :angular --ts 1 --html 2
@[code](@/content/guides/cell-features/selection/angular/example4.ts)
@[code](@/content/guides/cell-features/selection/angular/example4.html)
:::
:::
::: only-for vue
::: example #example4 :vue3
@[code](@/content/guides/cell-features/selection/vue/example4.vue)
:::
:::
Unfortunately, there is no easy way to change the border color of the selection.
### Add a class name to selected cells
Use the [`afterDrawSelection`](@/api/hooks.md#afterdrawselection) hook to give selected cells your own class name. Handsontable calls the hook for each cell of the selection area while it draws the selection. If the hook returns a string, Handsontable adds that string as a class name to the cell. If the hook returns anything else, the cell gets no extra class.
The hook receives the visual row and column index of the cell, the corners of the current selection layer, and the number of the layer being drawn.
## Resize a selection with handles
When you set [`selectionHandles`](@/api/options.md#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:
```javascript
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 [`selectionMode`](@/api/options.md#selectionmode) is `'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`](@/api/options.md#fixedrowstop), [`fixedRowsBottom`](@/api/options.md#fixedrowsbottom), [`fixedColumnsStart`](@/api/options.md#fixedcolumnsstart)).
- The handles are pointer-only -- there is no keyboard equivalent. To resize a selection with the keyboard, use Shift+Arrow.
::: only-for javascript
::: example #example7 --html 1 --js 2 --ts 3
@[code](@/content/guides/cell-features/selection/javascript/example7.html)
@[code](@/content/guides/cell-features/selection/javascript/example7.js)
@[code](@/content/guides/cell-features/selection/javascript/example7.ts)
:::
:::
::: only-for react
::: example #example7 :react --js 1 --ts 2
@[code](@/content/guides/cell-features/selection/react/example7.jsx)
@[code](@/content/guides/cell-features/selection/react/example7.tsx)
:::
:::
::: only-for angular
::: example #example7 :angular --ts 1 --html 2
@[code](@/content/guides/cell-features/selection/angular/example7.ts)
@[code](@/content/guides/cell-features/selection/angular/example7.html)
:::
:::
::: only-for vue
::: example #example7 :vue3
@[code](@/content/guides/cell-features/selection/vue/example7.vue)
:::
:::
## Move a selection by dragging
When you set [`moveCells`](@/api/options.md#movecells) to `true`, you can drag the border of a selected cell range to move or copy the range's data to a new location. For configuration details, limitations, hooks, and a live demo, see the [Move cells](@/guides/cell-features/move-cells/move-cells.md) guide. To move whole rows or columns instead, see the [Row moving](@/guides/rows/row-moving/row-moving.md) and [Column moving](@/guides/columns/column-moving/column-moving.md) guides.
## Select cells programmatically
Use [`selectCell()`](@/api/core.md#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()`](@/api/core.md#deselectcell) to clear the selection.
::: only-for javascript
::: example #example5 --html 1 --js 2 --ts 3
@[code](@/content/guides/cell-features/selection/javascript/example5.html)
@[code](@/content/guides/cell-features/selection/javascript/example5.js)
@[code](@/content/guides/cell-features/selection/javascript/example5.ts)
:::
:::
::: only-for react
::: example #example5 :react --js 1 --ts 2
@[code](@/content/guides/cell-features/selection/react/example5.jsx)
@[code](@/content/guides/cell-features/selection/react/example5.tsx)
:::
:::
::: only-for angular
::: example #example5 :angular --ts 1 --html 2
@[code](@/content/guides/cell-features/selection/angular/example5.ts)
@[code](@/content/guides/cell-features/selection/angular/example5.html)
:::
:::
::: only-for vue
::: example #example5 :vue3
@[code](@/content/guides/cell-features/selection/vue/example5.vue)
:::
:::
## 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 [`autoWrapRow`](@/api/options.md#autowraprow) configuration option to `true`.
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 [`autoWrapCol`](@/api/options.md#autowrapcol) configuration option to `true`.
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.
::: only-for javascript
::: example #example6 --html 1 --js 2 --ts 3
@[code](@/content/guides/cell-features/selection/javascript/example6.html)
@[code](@/content/guides/cell-features/selection/javascript/example6.js)
@[code](@/content/guides/cell-features/selection/javascript/example6.ts)
:::
:::
::: only-for react
::: example #example6 :react --js 1 --ts 2
@[code](@/content/guides/cell-features/selection/react/example6.jsx)
@[code](@/content/guides/cell-features/selection/react/example6.tsx)
:::
:::
::: only-for angular
::: example #example6 :angular --ts 1 --html 2
@[code](@/content/guides/cell-features/selection/angular/example6.ts)
@[code](@/content/guides/cell-features/selection/angular/example6.html)
:::
:::
::: only-for vue
::: example #example6 :vue3
@[code](@/content/guides/cell-features/selection/vue/example6.vue)
:::
:::
## 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](@/guides/internationalization/layout-direction/layout-direction.md).
** In case of multiple selection layers, only the last selection layer gets extended.
## Related API reference
**Configuration options**
- [autoWrapCol](@/api/options.md#autowrapcol)
- [autoWrapRow](@/api/options.md#autowraprow)
- [fragmentSelection](@/api/options.md#fragmentselection)
- [disableVisualSelection](@/api/options.md#disablevisualselection)
- [dragToScroll](@/api/options.md#dragtoscroll)
- [moveCells](@/api/options.md#movecells)
- [selectionHandles](@/api/options.md#selectionhandles)
- [selectionMode](@/api/options.md#selectionmode)
- [outsideClickDeselects](@/api/options.md#outsideclickdeselects)
**Core methods**
- [deselectCell()](@/api/core.md#deselectcell)
- [getSelected()](@/api/core.md#getselected)
- [getSelectedLast()](@/api/core.md#getselectedlast)
- [getSelectedRange()](@/api/core.md#getselectedrange)
- [getSelectedRangeLast()](@/api/core.md#getselectedrangelast)
- [selectAll()](@/api/core.md#selectall)
- [selectCell()](@/api/core.md#selectcell)
- [selectCells()](@/api/core.md#selectcells)
- [selectColumns()](@/api/core.md#selectcolumns)
- [selectRows()](@/api/core.md#selectrows)
**Hooks**
- [afterMoveCells](@/api/hooks.md#aftermovecells)
- [afterDeselect](@/api/hooks.md#afterdeselect)
- [afterDrawSelection](@/api/hooks.md#afterdrawselection)
- [afterModifyTransformEnd](@/api/hooks.md#aftermodifytransformend)
- [afterModifyTransformStart](@/api/hooks.md#aftermodifytransformstart)
- [afterSelection](@/api/hooks.md#afterselection)
- [afterSelectionByProp](@/api/hooks.md#afterselectionbyprop)
- [afterSelectionEnd](@/api/hooks.md#afterselectionend)
- [afterSelectionEndByProp](@/api/hooks.md#afterselectionendbyprop)
- [beforeMoveCells](@/api/hooks.md#beforemovecells)
- [afterOnSelectionHandleMouseDown](@/api/hooks.md#afteronselectionhandlemousedown)
- [modifyTransformStart](@/api/hooks.md#modifytransformstart)
**Plugins**
- [DragToScroll](@/api/dragToScroll.md)
Microsoft and Excel are registered trademarks of Microsoft Corporation. Google Sheets is a trademark of Google LLC.
---
# Move cells
URL: https://handsontable.com/docs/javascript-data-grid/move-cells/
Added in Handsontable 18.1.0.
Move or copy a selected range of cells by dragging its border, the same way you would in a spreadsheet application.
[[toc]]
## Overview
When you set [`moveCells`](@/api/options.md#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.
The `moveCells` option was introduced in Handsontable 18.1.0. To move whole rows or columns instead of a cell range, see the [Row moving](@/guides/rows/row-moving/row-moving.md) and [Column moving](@/guides/columns/column-moving/column-moving.md) guides.
## Enable cell moving
To enable drag-to-move, set the [`moveCells`](@/api/options.md#movecells) option to `true`:
```javascript
moveCells: true,
```
This option applies at the grid level and defaults to `false`.
Select a range of cells in the demo below, then drag its border to a new location. Hold **Ctrl** or ⌘ to copy the range instead of moving it.
::: only-for javascript
::: example #example1 --html 1 --js 2 --ts 3
@[code](@/content/guides/cell-features/move-cells/javascript/example1.html)
@[code](@/content/guides/cell-features/move-cells/javascript/example1.js)
@[code](@/content/guides/cell-features/move-cells/javascript/example1.ts)
:::
:::
::: only-for react
::: example #example1 :react --js 1 --ts 2
@[code](@/content/guides/cell-features/move-cells/react/example1.jsx)
@[code](@/content/guides/cell-features/move-cells/react/example1.tsx)
:::
:::
::: only-for angular
::: example #example1 :angular --ts 1 --html 2
@[code](@/content/guides/cell-features/move-cells/angular/example1.ts)
@[code](@/content/guides/cell-features/move-cells/angular/example1.html)
:::
:::
::: only-for vue
::: example #example1 :vue3
@[code](@/content/guides/cell-features/move-cells/vue/example1.vue)
:::
:::
## Moving cells with formulas
When the [`formulas`](@/api/options.md#formulas) plugin is active, formula references adjust automatically on move -- the same way they do in Excel.
## 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 [`disableVisualSelection`](@/api/options.md#disablevisualselection) is set.
- A move that would split a merged cell is blocked.
## Hooks
- [`beforeMoveCells`](@/api/hooks.md#beforemovecells) fires before the data relocates. Return `false` from the handler to cancel the move.
- [`afterMoveCells`](@/api/hooks.md#aftermovecells) fires after the data has been relocated.
## Move cells programmatically
To move or copy a range from code, call the plugin's `moveCellRange()` method:
```javascript
hot.getPlugin('moveCells').moveCellRange(sourceRange, targetTopLeft, isCopy);
```
## Related articles
**Related guides**
- [Selection](@/guides/cell-features/selection/selection.md)
- [Row moving](@/guides/rows/row-moving/row-moving.md)
- [Column moving](@/guides/columns/column-moving/column-moving.md)
**Configuration options**
- [moveCells](@/api/options.md#movecells)
- [disableVisualSelection](@/api/options.md#disablevisualselection)
**Hooks**
- [afterMoveCells](@/api/hooks.md#aftermovecells)
- [beforeMoveCells](@/api/hooks.md#beforemovecells)
**Plugins**
- [MoveCells](@/api/moveCells.md)
Microsoft and Excel are registered trademarks of Microsoft Corporation.
---
# Merge cells
URL: https://handsontable.com/docs/javascript-data-grid/merge-cells/
Merge adjacent cells, using the **Ctrl**+**M** shortcut or the context menu. Control merged cells, using Handsontable's API.
[[toc]]
## Overview
By merging, you can combine two or more adjacent cells into a single cell that spans several rows or columns.
Handsontable merges cells in the same way as Microsoft Excel: keeps only the upper-left value of the selected range and clears other values. This clearing happens in the underlying data, not just on screen -- see [Effect on the underlying data](#effect-on-the-underlying-data).
## How to merge cells
To enable the merge cells feature, set the [`mergeCells`](@/api/options.md#mergecells) option to `true` or to an array.
To initialize Handsontable with predefined merged cells, provide merged cells details in form of an array:
The `row` and `col` properties use visual indexes. They refer to positions as displayed in the grid, in the same coordinate space as `selectCell()` and `getCellMeta()`.
::: only-for javascript
```js
mergeCells: [
{ row: 1, col: 1, rowspan: 3, colspan: 3 },
{ row: 3, col: 4, rowspan: 2, colspan: 2 },
{ row: 5, col: 6, rowspan: 3, colspan: 3 },
]
```
:::
::: only-for react
```jsx
mergeCells={[
{ row: 1, col: 1, rowspan: 3, colspan: 3 },
{ row: 3, col: 4, rowspan: 2, colspan: 2 },
{ row: 5, col: 6, rowspan: 3, colspan: 3 },
]}
```
:::
::: only-for angular
```ts
settings = {
mergeCells: [
{ row: 1, col: 1, rowspan: 3, colspan: 3 },
{ row: 3, col: 4, rowspan: 2, colspan: 2 },
{ row: 5, col: 6, rowspan: 3, colspan: 3 },
],
};
```
:::
::: only-for vue
```js
const hotSettings = {
mergeCells: [
{ row: 1, col: 1, rowspan: 3, colspan: 3 },
{ row: 3, col: 4, rowspan: 2, colspan: 2 },
{ row: 5, col: 6, rowspan: 3, colspan: 3 },
],
};
```
:::
::: only-for javascript
::: example #example1 --js 1 --ts 2
@[code](@/content/guides/cell-features/merge-cells/javascript/example1.js)
@[code](@/content/guides/cell-features/merge-cells/javascript/example1.ts)
:::
:::
::: only-for react
::: example #example1 :react --js 1 --ts 2
@[code](@/content/guides/cell-features/merge-cells/react/example1.jsx)
@[code](@/content/guides/cell-features/merge-cells/react/example1.tsx)
:::
:::
::: only-for angular
::: example #example1 :angular --ts 1 --html 2
@[code](@/content/guides/cell-features/merge-cells/angular/example1.ts)
@[code](@/content/guides/cell-features/merge-cells/angular/example1.html)
:::
:::
::: only-for vue
::: example #example1 :vue3
@[code](@/content/guides/cell-features/merge-cells/vue/example1.vue)
:::
:::
## Optimizing rendering of the wide/tall merged cells
When cells span thousands of rows or columns, scrolling may feel slower compared to unmerged cells. To improve performance, consider enabling the dedicated virtualization feature for merged cells, which is disabled by default.
To enable the merged cells virtualization mode, enable the `virtualized` option:
::: only-for javascript
```js
mergeCells: {
virtualized: true,
cells: [{ row: 1, col: 1, rowspan: 200, colspan: 2 }]
}
```
:::
::: only-for react
```jsx
mergeCells={{
virtualized: true,
cells: [{ row: 1, col: 1, rowspan: 200, colspan: 2 }]
}}
```
:::
::: only-for angular
```ts
settings = {
mergeCells: {
virtualized: true,
cells: [{ row: 1, col: 1, rowspan: 200, colspan: 2 }],
},
};
```
:::
::: only-for vue
```js
const hotSettings = {
mergeCells: {
virtualized: true,
cells: [{ row: 1, col: 1, rowspan: 200, colspan: 2 }],
},
};
```
:::
The example below uses virtualized merged cells. It's also recommended to increase the buffer of rendered rows/columns to minimize the flickering effects.
::: only-for javascript
::: example #example2 --js 1 --ts 2
@[code](@/content/guides/cell-features/merge-cells/javascript/example2.js)
@[code](@/content/guides/cell-features/merge-cells/javascript/example2.ts)
:::
:::
::: only-for react
::: example #example2 :react --js 1 --ts 2
@[code](@/content/guides/cell-features/merge-cells/react/example2.jsx)
@[code](@/content/guides/cell-features/merge-cells/react/example2.tsx)
:::
:::
::: only-for angular
::: example #example2 :angular --ts 1 --html 2
@[code](@/content/guides/cell-features/merge-cells/angular/example2.ts)
@[code](@/content/guides/cell-features/merge-cells/angular/example2.html)
:::
:::
::: only-for vue
::: example #example2 :vue3
@[code](@/content/guides/cell-features/merge-cells/vue/example2.vue)
:::
:::
## React to merge and unmerge events
To run your own logic when cells merge or unmerge, use the [`beforeMergeCells`](@/api/hooks.md#beforemergecells), [`afterMergeCells`](@/api/hooks.md#aftermergecells), [`beforeUnmergeCells`](@/api/hooks.md#beforeunmergecells), and [`afterUnmergeCells`](@/api/hooks.md#afterunmergecells) hooks. Each hook receives the affected `cellRange`, and `afterMergeCells` also receives the resulting `mergeParent` object (`row`, `col`, `rowspan`, `colspan`).
The example below logs a message every time you merge or unmerge cells, using the context menu or the **Ctrl**+**M** shortcut.
::: only-for javascript
::: example #example3 --html 1 --js 2 --ts 3
@[code](@/content/guides/cell-features/merge-cells/javascript/example3.html)
@[code](@/content/guides/cell-features/merge-cells/javascript/example3.js)
@[code](@/content/guides/cell-features/merge-cells/javascript/example3.ts)
:::
:::
::: only-for react
::: example #example3 :react --js 1 --ts 2
@[code](@/content/guides/cell-features/merge-cells/react/example3.jsx)
@[code](@/content/guides/cell-features/merge-cells/react/example3.tsx)
:::
:::
::: only-for angular
::: example #example3 :angular --ts 1 --html 2
@[code](@/content/guides/cell-features/merge-cells/angular/example3.ts)
@[code](@/content/guides/cell-features/merge-cells/angular/example3.html)
:::
:::
::: only-for vue
::: example #example3 :vue3
@[code](@/content/guides/cell-features/merge-cells/vue/example3.vue)
:::
:::
## Merge and unmerge cells programmatically
To merge or unmerge a range without a user action, call the [`MergeCells`](@/api/mergeCells.md) plugin's `merge()` and `unmerge()` methods. Both methods take a visual `startRow`, `startColumn`, `endRow`, and `endColumn`, the same coordinate space as `selectCell()`.
```js
hot.getPlugin('mergeCells').merge(startRow, startColumn, endRow, endColumn);
hot.getPlugin('mergeCells').unmerge(startRow, startColumn, endRow, endColumn);
```
This clears the underlying data the same way as merging through the UI or the [`mergeCells`](@/api/options.md#mergecells) configuration option. See [Effect on the underlying data](#effect-on-the-underlying-data).
The example below merges and unmerges a footnote row that spans every column, using buttons instead of a manual selection.
::: only-for javascript
::: example #example4 --html 1 --js 2 --ts 3
@[code](@/content/guides/cell-features/merge-cells/javascript/example4.html)
@[code](@/content/guides/cell-features/merge-cells/javascript/example4.js)
@[code](@/content/guides/cell-features/merge-cells/javascript/example4.ts)
:::
:::
::: only-for react
::: example #example4 :react --js 1 --ts 2
@[code](@/content/guides/cell-features/merge-cells/react/example4.jsx)
@[code](@/content/guides/cell-features/merge-cells/react/example4.tsx)
:::
:::
::: only-for angular
::: example #example4 :angular --ts 1 --html 2
@[code](@/content/guides/cell-features/merge-cells/angular/example4.ts)
@[code](@/content/guides/cell-features/merge-cells/angular/example4.html)
:::
:::
::: only-for vue
::: example #example4 :vue3
@[code](@/content/guides/cell-features/merge-cells/vue/example4.vue)
:::
:::
## Effect on the underlying data
Merging doesn't only hide the covered cells visually -- it clears their values in the underlying data too, whether you merge through the UI, the [`mergeCells`](@/api/options.md#mergecells) configuration option, or the `merge()` method. Only the top-left cell of the range keeps its value; every other cell covered by the merge is set to `null`.
For a range merged at `(0, 0)` with `rowspan: 2` and `colspan: 2`:
```js
hot.getData();
// -> [['Top-left value', null], [null, null]]
hot.getSourceData();
// -> [['Top-left value', null], [null, null]] (the same)
```
Both [`getData()`](@/api/core.md#getdata) and [`getSourceData()`](@/api/core.md#getsourcedata) return `null` for the covered cells, because the clearing runs through the normal change pipeline: it fires [`beforeChange`](@/api/hooks.md#beforechange) and [`afterChange`](@/api/hooks.md#afterchange) with `source === 'MergeCells'` (see [Events and hooks: Definition for `source` argument](@/guides/getting-started/events-and-hooks/events-and-hooks.md#definition-for-source-argument)).
Unmerging does not restore the cleared values. If you need the original values back, keep a copy before merging, or restore them yourself in a [`beforeUnmergeCells`](@/api/hooks.md#beforeunmergecells) or [`afterUnmergeCells`](@/api/hooks.md#afterunmergecells) handler.
## Effect on viewport getter methods
With merged cells, the rendered range extends to fit any merged cell that crosses the viewport edge. This is the same expansion that the `virtualized` option turns off. As a result, the rendered-range getters can return indexes beyond what you see on the screen:
- [`getFirstRenderedVisibleRow()`](@/api/core.md#getfirstrenderedvisiblerow), [`getLastRenderedVisibleRow()`](@/api/core.md#getlastrenderedvisiblerow), [`getFirstRenderedVisibleColumn()`](@/api/core.md#getfirstrenderedvisiblecolumn), and [`getLastRenderedVisibleColumn()`](@/api/core.md#getlastrenderedvisiblecolumn).
- [`AutoRowSize.getFirstVisibleRow()`](@/api/autoRowSize.md#getfirstvisiblerow) and [`AutoRowSize.getLastVisibleRow()`](@/api/autoRowSize.md#getlastvisiblerow), which delegate to the rendered-row getters.
- [`AutoColumnSize.getFirstVisibleColumn()`](@/api/autoColumnSize.md#getfirstvisiblecolumn) and [`AutoColumnSize.getLastVisibleColumn()`](@/api/autoColumnSize.md#getlastvisiblecolumn), which delegate to the rendered-column getters.
For example, a merged cell that spans columns 0 to 100 makes `getLastVisibleColumn()` return an index near 100, even when the viewport shows far fewer columns.
To read the actual visible viewport, use the fully-visible or partially-visible getters, which ignore the merge-cell expansion:
- [`getFirstFullyVisibleRow()`](@/api/core.md#getfirstfullyvisiblerow), [`getLastFullyVisibleRow()`](@/api/core.md#getlastfullyvisiblerow), [`getFirstFullyVisibleColumn()`](@/api/core.md#getfirstfullyvisiblecolumn), and [`getLastFullyVisibleColumn()`](@/api/core.md#getlastfullyvisiblecolumn).
- [`getFirstPartiallyVisibleRow()`](@/api/core.md#getfirstpartiallyvisiblerow), [`getLastPartiallyVisibleRow()`](@/api/core.md#getlastpartiallyvisiblerow), [`getFirstPartiallyVisibleColumn()`](@/api/core.md#getfirstpartiallyvisiblecolumn), and [`getLastPartiallyVisibleColumn()`](@/api/core.md#getlastpartiallyvisiblecolumn).
Setting `virtualized` to `true` also removes the range expansion, but it is a performance option -- the rendered-range getters still include buffered rows and columns outside the viewport.
## Behavior during row/column reorder and column freeze
When a merged cell's underlying rows or columns are reordered (through [`manualColumnMove`](@/api/options.md#manualcolumnmove), [`manualRowMove`](@/api/options.md#manualrowmove), or [`manualColumnFreeze`](@/api/options.md#manualcolumnfreeze)), Handsontable follows the merge to the new visual position. Two side effects can occur:
- **Auto-split**: if the move bisects a merge so the underlying cells are no longer contiguous in the new visual order, the merge is split into separate merges, one per contiguous run. The cross-axis span (`rowspan` for column moves, `colspan` for row moves) is preserved on every fragment.
- **Silent drop of single-cell fragments**: any resulting fragment that ends up as a single cell (`rowspan === 1 && colspan === 1`) is removed, because a single cell is no longer a merge. The [`afterMergeCells`](@/api/hooks.md#aftermergecells) hook is not fired for the dropped fragment.
[`undo`](@/api/options.md#undo) and [`redo`](@/api/options.md#redo) restore the pre-move state, including any merges that were split or dropped by the reorder.
## Result
Cells at the configured positions are now merged. Users see a single cell spanning multiple rows or columns.
## Related keyboard shortcuts
| Windows | macOS | Action | Excel | Sheets |
| -------------------------------------- | -------------------------------------- | ----------------------------------- | :-----: | :-----: |
| **Ctrl**+**M** | ⌃+**M** | Merge or unmerge the selected cells | ✗ | ✗ |
## Related API reference
**Configuration options**
- [mergeCells](@/api/options.md#mergecells)
- [viewportColumnRenderingThreshold](@/api/options.md#viewportcolumnrenderingthreshold)
- [viewportRowRenderingThreshold](@/api/options.md#viewportrowrenderingthreshold)
- [viewportColumnRenderingOffset](@/api/options.md#viewportcolumnrenderingoffset)
- [viewportRowRenderingOffset](@/api/options.md#viewportrowrenderingoffset)
**Hooks**
- [afterMergeCells](@/api/hooks.md#aftermergecells)
- [afterUnmergeCells](@/api/hooks.md#afterunmergecells)
- [beforeMergeCells](@/api/hooks.md#beforemergecells)
- [beforeUnmergeCells](@/api/hooks.md#beforeunmergecells)
**Plugins**
- [MergeCells](@/api/mergeCells.md)
---
# Conditional formatting
URL: https://handsontable.com/docs/javascript-data-grid/conditional-formatting/
Style cells dynamically based on their values, so you can highlight outliers, flag errors, or visualize data ranges.
[[toc]]
## Overview
Conditional formatting changes a cell's appearance based on the data it holds. As values change, the formatting updates on the next render.
Choose an approach based on your goal:
- Use [`className`](@/api/options.md#classname) with the [`cells`](@/api/options.md#cells) callback when you want reusable CSS classes driven by cell values. This is the recommended default.
- Use the [`renderer`](@/api/options.md#renderer) option when you need inline styles or want to transform the displayed value.
- Combine a `renderer` with a computed background color when you want a color scale that reflects each value's magnitude.
For static styling that does not depend on cell values, see [Formatting cells](@/guides/cell-features/formatting-cells/formatting-cells.md).
## Prerequisites
- A Handsontable instance with data loaded.
- A stylesheet, if you format cells through custom CSS classes.
- Familiarity with the cascading configuration model, where cell settings override column settings, which override global settings. See [Configuration options](@/guides/getting-started/configuration-options/configuration-options.md).
## Highlight cells by value with a CSS class
Return a [`className`](@/api/options.md#classname) from the [`cells`](@/api/options.md#cells) callback to style cells by value. The callback runs for every cell, so you can inspect the value and assign a class conditionally.
This example uses the cascading model on two levels: the Company column sets a static `className` through its `columns` entry, while the [`cells`](@/api/options.md#cells) callback adds a `loss` class to negative quarters and a `strong-quarter` class to quarters above 10. The `loss` class pairs its red color with a leading marker, so the value stays readable without color.
::: only-for javascript
::: example #example1 --css 1 --js 2 --ts 3
@[code](@/content/guides/cell-features/conditional-formatting/javascript/example1.css)
@[code](@/content/guides/cell-features/conditional-formatting/javascript/example1.js)
@[code](@/content/guides/cell-features/conditional-formatting/javascript/example1.ts)
:::
:::
::: only-for react
::: example #example1 :react --css 1 --js 2 --ts 3
@[code](@/content/guides/cell-features/conditional-formatting/react/example1.css)
@[code](@/content/guides/cell-features/conditional-formatting/react/example1.jsx)
@[code](@/content/guides/cell-features/conditional-formatting/react/example1.tsx)
:::
:::
::: only-for angular
::: example #example1 :angular --ts 1 --html 2
@[code](@/content/guides/cell-features/conditional-formatting/angular/example1.ts)
@[code](@/content/guides/cell-features/conditional-formatting/angular/example1.html)
:::
:::
::: only-for vue
::: example #example1 :vue3
@[code](@/content/guides/cell-features/conditional-formatting/vue/example1.vue)
:::
:::
## Format cells with a custom renderer
Use a [`renderer`](@/api/options.md#renderer) when a CSS class is not enough - for example, when you need to transform the displayed value or apply inline styles computed at render time.
This renderer formats each quarter as a currency amount and shows losses in an accounting format, `($1.3M)`. The parentheses convey a loss on their own, so the red color is a secondary signal.
::: only-for javascript
::: example #example2 --css 1 --js 2 --ts 3
@[code](@/content/guides/cell-features/conditional-formatting/javascript/example2.css)
@[code](@/content/guides/cell-features/conditional-formatting/javascript/example2.js)
@[code](@/content/guides/cell-features/conditional-formatting/javascript/example2.ts)
:::
:::
::: only-for react
::: example #example2 :react --css 1 --js 2 --ts 3
@[code](@/content/guides/cell-features/conditional-formatting/react/example2.css)
@[code](@/content/guides/cell-features/conditional-formatting/react/example2.jsx)
@[code](@/content/guides/cell-features/conditional-formatting/react/example2.tsx)
:::
:::
::: only-for angular
::: example #example2 :angular --ts 1 --html 2
@[code](@/content/guides/cell-features/conditional-formatting/angular/example2.ts)
@[code](@/content/guides/cell-features/conditional-formatting/angular/example2.html)
:::
:::
::: only-for vue
::: example #example2 :vue3
@[code](@/content/guides/cell-features/conditional-formatting/vue/example2.vue)
:::
:::
## Build a color scale
To visualize magnitude, compute a background color from each value and apply it in a [`renderer`](@/api/options.md#renderer). This example maps every quarter to a green-to-red scale based on the minimum and maximum across the data, producing a heatmap. The numeric value remains visible in each cell, so the color adds meaning rather than replacing it.
::: only-for javascript
::: example #example3 --js 1 --ts 2
@[code](@/content/guides/cell-features/conditional-formatting/javascript/example3.js)
@[code](@/content/guides/cell-features/conditional-formatting/javascript/example3.ts)
:::
:::
::: only-for react
::: example #example3 :react --js 1 --ts 2
@[code](@/content/guides/cell-features/conditional-formatting/react/example3.jsx)
@[code](@/content/guides/cell-features/conditional-formatting/react/example3.tsx)
:::
:::
::: only-for angular
::: example #example3 :angular --ts 1 --html 2
@[code](@/content/guides/cell-features/conditional-formatting/angular/example3.ts)
@[code](@/content/guides/cell-features/conditional-formatting/angular/example3.html)
:::
:::
::: only-for vue
::: example #example3 :vue3
@[code](@/content/guides/cell-features/conditional-formatting/vue/example3.vue)
:::
:::
## Make conditional formatting accessible
Color alone does not satisfy [WCAG 2.2 Success Criterion 1.4.1 Use of Color](https://www.w3.org/WAI/WCAG22/quickref/#use-of-color), a Level A criterion. Readers with color vision deficiencies, or anyone viewing the grid in high-contrast mode, can miss a signal that relies only on color.
- Pair color with a second cue: a symbol, text label, or font weight. In the first example, the `loss` class adds a marker; in the second, losses appear in parentheses.
- In a color scale, keep the underlying value visible so the color stays supplementary.
- Verify that text keeps a contrast ratio of at least 4.5:1 against the cell background you apply.
## Result
Cells that match your conditions display the configured styles - classes, inline styles, or a color scale - and update automatically as the data changes.
## Related articles
**Related guides**
- [Formatting cells](@/guides/cell-features/formatting-cells/formatting-cells.md)
- [Text alignment](@/guides/cell-features/text-alignment/text-alignment.md)
- [Configuration options](@/guides/getting-started/configuration-options/configuration-options.md)
**Configuration options**
- [className](@/api/options.md#classname)
- [cells](@/api/options.md#cells)
- [renderer](@/api/options.md#renderer)
---
# Text alignment
URL: https://handsontable.com/docs/javascript-data-grid/text-alignment/
Align values within cells: horizontally (to the right, left, center, or by justifying them), and vertically (to the top, middle, or bottom of the cell).
[[toc]]
Apply text alignment to cells using CSS class names or the `className` configuration option.
## Overview
You can set cell alignment in two ways:
| Configuration option
`className`/`cells` | Context menu
`alignment` item |
|---|---|
| Set programmatically, for the whole grid, a column, or individual cells | Set interactively, by the person using the grid |
| Requires no additional plugin | Requires the [`ContextMenu`](@/guides/accessories-and-menus/context-menu/context-menu.md) plugin, with the `alignment` item enabled |
| Not tracked by [`UndoRedo`](@/guides/accessories-and-menus/undo-redo/undo-redo.md) | Tracked by [`UndoRedo`](@/guides/accessories-and-menus/undo-redo/undo-redo.md) |
## To align a cell
To set alignment for individual cells, configure them using the [`cells`](@/api/options.md#cells) option or the [`cell`](@/api/options.md#cell) array. Available class names:
- Horizontal: `htLeft`, `htCenter`, `htRight`, `htJustify`
- Vertical: `htTop`, `htMiddle`, `htBottom`
You can track alignment changes by using the [`afterSetCellMeta`](@/api/hooks.md#aftersetcellmeta) hook.
## To align a column
To apply alignment globally or per column, provide the alignment details in the [`className`](@/api/options.md#classname) option, for example:
::: only-for javascript
```js
className: 'htCenter'
```
:::
::: only-for react
```jsx
className="htCenter"
```
:::
::: only-for angular
```ts
settings = { className: "htCenter" };
```
:::
::: only-for vue
```js
const hotSettings = {
className: 'htCenter',
};
```
:::
## Align cells using the context menu
Let the person using the grid set alignment interactively, through the [context menu](@/guides/accessories-and-menus/context-menu/context-menu.md).
Enable the `ContextMenu` plugin and include the `alignment` item:
```js
contextMenu: ['alignment'],
```
Select one or more cells, right-click to open the context menu, and choose an option from the **Align** submenu:
- Horizontal: **Left**, **Center**, **Right**, **Justify**
- Vertical: **Top**, **Middle**, **Bottom**
Each option sets the matching CSS class name (`htLeft`, `htCenter`, `htRight`, `htJustify`, `htTop`, `htMiddle`, `htBottom`) on the selected cells, and fires the [`beforeCellAlignment`](@/api/hooks.md#beforecellalignment) hook before applying the change.
### Undo and redo alignment changes
Alignment changes made through the context menu are tracked by the [`UndoRedo`](@/guides/accessories-and-menus/undo-redo/undo-redo.md) plugin. Press **Ctrl**/⌘+**Z** to undo an alignment change, and **Ctrl**/⌘+**Y** to redo it.
Alignment changes made through the `className` or `cells` configuration options aren't tracked by `UndoRedo`.
## Basic example
The following code sample configures the grid to use `htCenter` and configures individual cells to use different alignments.
::: only-for javascript
::: example #example1 --js 1 --ts 2
@[code](@/content/guides/cell-features/text-alignment/javascript/example1.js)
@[code](@/content/guides/cell-features/text-alignment/javascript/example1.ts)
:::
:::
::: only-for react
::: example #example1 :react --js 1 --ts 2
@[code](@/content/guides/cell-features/text-alignment/react/example1.jsx)
@[code](@/content/guides/cell-features/text-alignment/react/example1.tsx)
:::
:::
::: only-for angular
::: example #example1 :angular --ts 1 --html 2
@[code](@/content/guides/cell-features/text-alignment/angular/example1.ts)
@[code](@/content/guides/cell-features/text-alignment/angular/example1.html)
:::
:::
::: only-for vue
::: example #example1 :vue3
@[code](@/content/guides/cell-features/text-alignment/vue/example1.vue)
:::
:::
## Result
Cells display the configured horizontal or vertical alignment. Global settings apply to all cells, and per-cell settings take precedence over the global defaults.
## Related API reference
**Configuration options**
- [className](@/api/options.md#classname)
- [contextMenu](@/api/options.md#contextmenu)
**Hooks**
- [afterSetCellMeta](@/api/hooks.md#aftersetcellmeta)
- [beforeCellAlignment](@/api/hooks.md#beforecellalignment)
**Plugins**
- [ContextMenu](@/api/contextMenu.md)
- [UndoRedo](@/api/undoRedo.md)
---
# Read-only cells
URL: https://handsontable.com/docs/javascript-data-grid/read-only-cells/
Make specified cells read-only to protect them from unwanted changes but still allow navigation and copying of data.
[[toc]]
Disable individual cells, entire columns, or entire rows to prevent user edits. Use `readOnly` on cells, columns, or the whole grid.
## Overview
Disabling a cell makes the cell read-only or non-editable. Both have similar outcomes, with the following differences:
| Read-only cell
`readOnly: true` | Non-editable cell
`editor: false` |
|------------------------------------------------------------------------------| -------------------------------------------------------------------------- |
| Has an additional CSS class (`htDimmed`) | Has no additional CSS class |
| Copy works, paste doesn't work | Copy-paste works |
| Drag-to-fill doesn't work | Drag-to-fill works |
| Can't be changed by [`populateFromArray()`](@/api/core.md#populatefromarray) | Can be changed by [`populateFromArray()`](@/api/core.md#populatefromarray) |
## Make the grid read-only
To make the entire grid read-only, set [`readOnly`](@/api/options.md#readonly) to `true` as a [top-level grid option](@/guides/getting-started/configuration-options/configuration-options.md#set-grid-options).
::: only-for javascript
::: example #exampleReadOnlyGrid --js 1 --ts 2
@[code](@/content/guides/cell-features/read-only-cells/javascript/exampleReadOnlyGrid.js)
@[code](@/content/guides/cell-features/read-only-cells/javascript/exampleReadOnlyGrid.ts)
:::
:::
::: only-for react
::: example #exampleReadOnlyGrid :react --js 1 --ts 2
@[code](@/content/guides/cell-features/read-only-cells/react/exampleReadOnlyGrid.jsx)
@[code](@/content/guides/cell-features/read-only-cells/react/exampleReadOnlyGrid.tsx)
:::
:::
::: only-for angular
::: example #exampleReadOnlyGrid :angular --ts 1 --html 2
@[code](@/content/guides/cell-features/read-only-cells/angular/exampleReadOnlyGrid.ts)
@[code](@/content/guides/cell-features/read-only-cells/angular/exampleReadOnlyGrid.html)
:::
:::
::: only-for vue
::: example #exampleReadOnlyGrid :vue3
@[code](@/content/guides/cell-features/read-only-cells/vue/exampleReadOnlyGrid.vue)
:::
:::
## Make a column read-only
To make a column read-only, declare it in the [`columns`](@/api/options.md#columns) configuration option. The column remains available for keyboard navigation and copying data (**Ctrl**/**Cmd**+**C**), but editing and pasting are disabled. The example below also defines a custom renderer that dims the read-only column, giving the user a visual cue that its cells are read-only.
::: only-for javascript
::: example #example1 --js 1 --ts 2
@[code](@/content/guides/cell-features/read-only-cells/javascript/example1.js)
@[code](@/content/guides/cell-features/read-only-cells/javascript/example1.ts)
:::
:::
::: only-for react
::: example #example1 :react --js 1 --ts 2
@[code](@/content/guides/cell-features/read-only-cells/react/example1.jsx)
@[code](@/content/guides/cell-features/read-only-cells/react/example1.tsx)
:::
:::
::: only-for angular
::: example #example1 :angular --ts 1 --html 2
@[code](@/content/guides/cell-features/read-only-cells/angular/example1.ts)
@[code](@/content/guides/cell-features/read-only-cells/angular/example1.html)
:::
:::
::: only-for vue
::: example #example1 :vue3
@[code](@/content/guides/cell-features/read-only-cells/vue/example1.vue)
:::
:::
## Make a row read-only
To make an entire row read-only, use the [`cells`](@/api/options.md#cells) function and set the [`readOnly`](@/api/options.md#readonly) property based on the row index, regardless of the column. The example below makes the second row (index `1`) read-only.
::: only-for javascript
::: example #example5 --js 1 --ts 2
@[code](@/content/guides/cell-features/read-only-cells/javascript/example5.js)
@[code](@/content/guides/cell-features/read-only-cells/javascript/example5.ts)
:::
:::
::: only-for react
::: example #example5 :react --js 1 --ts 2
@[code](@/content/guides/cell-features/read-only-cells/react/example5.jsx)
@[code](@/content/guides/cell-features/read-only-cells/react/example5.tsx)
:::
:::
::: only-for angular
::: example #example5 :angular --ts 1 --html 2
@[code](@/content/guides/cell-features/read-only-cells/angular/example5.ts)
@[code](@/content/guides/cell-features/read-only-cells/angular/example5.html)
:::
:::
::: only-for vue
::: example #example5 :vue3
@[code](@/content/guides/cell-features/read-only-cells/vue/example5.vue)
:::
:::
## Make specific cells read-only
To make specific cells read-only, use the [`cells`](@/api/options.md#cells) function to set the [`readOnly`](@/api/options.md#readonly) property conditionally. The example below makes cells that contain the word "Nissan" read-only.
::: only-for javascript
::: example #example2 --js 1 --ts 2
@[code](@/content/guides/cell-features/read-only-cells/javascript/example2.js)
@[code](@/content/guides/cell-features/read-only-cells/javascript/example2.ts)
:::
:::
::: only-for react
::: example #example2 :react --js 1 --ts 2
@[code](@/content/guides/cell-features/read-only-cells/react/example2.jsx)
@[code](@/content/guides/cell-features/read-only-cells/react/example2.tsx)
:::
:::
::: only-for angular
::: example #example2 :angular --ts 1 --html 2
@[code](@/content/guides/cell-features/read-only-cells/angular/example2.ts)
@[code](@/content/guides/cell-features/read-only-cells/angular/example2.html)
:::
:::
::: only-for vue
::: example #example2 :vue3
@[code](@/content/guides/cell-features/read-only-cells/vue/example2.vue)
:::
:::
Non-editable cells behave like any other cells apart from preventing you from manually changing their values.
## Make a column non-editable
To make a column non-editable, declare it in the [`columns`](@/api/options.md#columns) configuration option. The column's basic behavior does not change -- you can still use keyboard navigation, **Ctrl**/**Cmd**+**C**, **Ctrl**/**Cmd**+**V**, and drag-to-fill. The example below also defines a custom renderer that dims the non-editable column, giving the user a visual cue that its cells are non-editable.
::: only-for javascript
::: example #example3 --js 1 --ts 2
@[code](@/content/guides/cell-features/read-only-cells/javascript/example3.js)
@[code](@/content/guides/cell-features/read-only-cells/javascript/example3.ts)
:::
:::
::: only-for react
::: example #example3 :react --js 1 --ts 2
@[code](@/content/guides/cell-features/read-only-cells/react/example3.jsx)
@[code](@/content/guides/cell-features/read-only-cells/react/example3.tsx)
:::
:::
::: only-for angular
::: example #example3 :angular --ts 1 --html 2
@[code](@/content/guides/cell-features/read-only-cells/angular/example3.ts)
@[code](@/content/guides/cell-features/read-only-cells/angular/example3.html)
:::
:::
::: only-for vue
::: example #example3 :vue3
@[code](@/content/guides/cell-features/read-only-cells/vue/example3.vue)
:::
:::
## Make specific cells non-editable
To make specific cells non-editable, set `editor: false` in the cell configuration. The following example shows a table with non-editable cells containing the word "Nissan".
::: only-for javascript
::: example #example4 --js 1 --ts 2
@[code](@/content/guides/cell-features/read-only-cells/javascript/example4.js)
@[code](@/content/guides/cell-features/read-only-cells/javascript/example4.ts)
:::
:::
::: only-for react
::: example #example4 :react --js 1 --ts 2
@[code](@/content/guides/cell-features/read-only-cells/react/example4.jsx)
@[code](@/content/guides/cell-features/read-only-cells/react/example4.tsx)
:::
:::
::: only-for angular
::: example #example4 :angular --ts 1 --html 2
@[code](@/content/guides/cell-features/read-only-cells/angular/example4.ts)
@[code](@/content/guides/cell-features/read-only-cells/angular/example4.html)
:::
:::
::: only-for vue
::: example #example4 :vue3
@[code](@/content/guides/cell-features/read-only-cells/vue/example4.vue)
:::
:::
## Block the entire grid
The [`readOnly`](@/api/options.md#readonly) and [`editor`](@/api/options.md#editor) options control editing, but users can still navigate and select cells. To block all interaction with the grid temporarily -- for example, during a loading state or a blocking workflow step -- use the [Dialog](@/guides/dialog/dialog/dialog.md) plugin. An open dialog overlays the grid and moves keyboard input into its own [focus scope](@/guides/navigation/focus-scopes/focus-scopes.md), so users can't reach the cells until the dialog closes. The grid regains focus automatically.
## Accessibility
When [`ariaTags`](@/api/options.md#ariatags) is enabled (the default), Handsontable adds `aria-readonly="true"` to the DOM element of every read-only cell, so screen readers announce that the cell can't be edited. Non-editable cells (`editor: false`) don't get this attribute, because Handsontable doesn't treat them as read-only in the data model -- only their editor is disabled.
For more accessibility features and testing guidance, see [Accessibility](@/guides/accessibility/accessibility/accessibility.md).
## Result
Read-only cells display with the `htDimmed` CSS class and block paste and drag-to-fill operations. Non-editable cells block manual editing but allow copy-paste and drag-to-fill.
## Related API reference
**Configuration options**
- [ariaTags](@/api/options.md#ariatags)
- [readOnly](@/api/options.md#readonly)
- [readOnlyCellClassName](@/api/options.md#readonlycellclassname)
---
# Comments
URL: https://handsontable.com/docs/javascript-data-grid/comments/
Add a comment (a note) to a cell, using the context menu, just like in Excel. Edit and delete comments. Make comments read-only.
[[toc]]
The Comments plugin lets users attach text notes to individual cells. Use it when reviewers need to annotate data without changing cell values.
The plugin shows and edits a comment in a text field, so it doesn't parse the comment as HTML. If you use the comment text elsewhere, for example in your own interface, escape or sanitize it first.
## Enable the plugin
Set the [`comments`](@/api/options.md#comments) configuration option to `true` to enable the feature and add all the needed context menu items. For example:
::: only-for javascript
```js
const hot = new Handsontable(container, {
data: [
['Update API docs', 'Ana García', 'In progress'],
['Deploy hotfix', 'James Okafor', 'Blocked'],
],
comments: true,
autoWrapRow: true,
autoWrapCol: true
});
```
:::
::: only-for react
```jsx
```
:::
::: only-for angular
```ts
data = [
["Update API docs", "Ana García", "In progress"],
["Deploy hotfix", "James Okafor", "Blocked"],
];
settings = {
comments: true,
};
```
```html
```
:::
::: only-for vue
```js
const hotSettings = {
data: [
['Update API docs', 'Ana García', 'In progress'],
['Deploy hotfix', 'James Okafor', 'Blocked'],
],
comments: true,
autoWrapRow: true,
autoWrapCol: true,
licenseKey: 'non-commercial-and-evaluation',
};
```
:::
## Add comments via the context menu
After you've enabled the plugin, the [Context Menu](@/guides/accessories-and-menus/context-menu/context-menu.md) gains a few new items:
- Add/Edit comment
- Delete comment
- Read-only comment
## Set up pre-set comments
You can also pre-define comments for your table. Comments are stored in the table's/column's/cell's metadata object and you can declare as any value of the respective type. For example:
::: only-for javascript
```js
cell: [
{ row: 1, col: 1, comment: { value: 'Hello world!' } }
]
```
:::
::: only-for react
```jsx
cell={[
{ row: 1, col: 1, comment: { value: 'Hello world!' } }
]}
```
:::
::: only-for angular
```ts
settings = {
cell: [{ row: 1, col: 1, comment: { value: "Hello world!" } }],
};
```
:::
::: only-for vue
```js
const hotSettings = {
cell: [
{ row: 1, col: 1, comment: { value: 'Hello world!' } },
],
};
```
:::
In this example, the comment "Hello world!" is added to the cell at `(1,1)`.
## Basic example
::: only-for javascript
::: example #example1 --js 1 --ts 2
@[code](@/content/guides/cell-features/comments/javascript/example1.js)
@[code](@/content/guides/cell-features/comments/javascript/example1.ts)
:::
:::
::: only-for react
::: example #example1 :react --js 1 --ts 2
@[code](@/content/guides/cell-features/comments/react/example1.jsx)
@[code](@/content/guides/cell-features/comments/react/example1.tsx)
:::
:::
::: only-for angular
::: example #example1 :angular --ts 1 --html 2
@[code](@/content/guides/cell-features/comments/angular/example1.ts)
@[code](@/content/guides/cell-features/comments/angular/example1.html)
:::
:::
::: only-for vue
::: example #example1 :vue3
@[code](@/content/guides/cell-features/comments/vue/example1.vue)
:::
:::
## Make a comment read-only
By default, all comments are editable. To change this, set the [`readOnly`](@/api/options.md#readonly) configuration option to `true` when adding a comment. This example makes the "Tesla" comment attached to a cell read-only, whereas the "Honda" comment attached to another cell is editable.
::: only-for javascript
::: example #example2 --js 1 --ts 2
@[code](@/content/guides/cell-features/comments/javascript/example2.js)
@[code](@/content/guides/cell-features/comments/javascript/example2.ts)
:::
:::
::: only-for react
::: example #example2 :react --js 1 --ts 2
@[code](@/content/guides/cell-features/comments/react/example2.jsx)
@[code](@/content/guides/cell-features/comments/react/example2.tsx)
:::
:::
::: only-for angular
::: example #example2 :angular --ts 1 --html 2
@[code](@/content/guides/cell-features/comments/angular/example2.ts)
@[code](@/content/guides/cell-features/comments/angular/example2.html)
:::
:::
::: only-for vue
::: example #example2 :vue3
@[code](@/content/guides/cell-features/comments/vue/example2.vue)
:::
:::
## Set a comment box's size
To set the width and height of a comment box, use the [`style`](@/api/options.md#comments) parameter.
::: only-for javascript
::: example #example3 --js 1 --ts 2
@[code](@/content/guides/cell-features/comments/javascript/example3.js)
@[code](@/content/guides/cell-features/comments/javascript/example3.ts)
:::
:::
::: only-for react
::: example #example3 :react --js 1 --ts 2
@[code](@/content/guides/cell-features/comments/react/example3.jsx)
@[code](@/content/guides/cell-features/comments/react/example3.tsx)
:::
:::
::: only-for angular
::: example #example3 :angular --ts 1 --html 2
@[code](@/content/guides/cell-features/comments/angular/example3.ts)
@[code](@/content/guides/cell-features/comments/angular/example3.html)
:::
:::
::: only-for vue
::: example #example3 :vue3
@[code](@/content/guides/cell-features/comments/vue/example3.vue)
:::
:::
## Set a delay for displaying comments
To display comments after a pre-configured time delay, use the [`displayDelay`](@/api/options.md#comments) parameter.
::: only-for javascript
::: example #example4 --js 1 --ts 2
@[code](@/content/guides/cell-features/comments/javascript/example4.js)
@[code](@/content/guides/cell-features/comments/javascript/example4.ts)
:::
:::
::: only-for react
::: example #example4 :react --js 1 --ts 2
@[code](@/content/guides/cell-features/comments/react/example4.jsx)
@[code](@/content/guides/cell-features/comments/react/example4.tsx)
:::
:::
::: only-for angular
::: example #example4 :angular --ts 1 --html 2
@[code](@/content/guides/cell-features/comments/angular/example4.ts)
@[code](@/content/guides/cell-features/comments/angular/example4.html)
:::
:::
::: only-for vue
::: example #example4 :vue3
@[code](@/content/guides/cell-features/comments/vue/example4.vue)
:::
:::
## Flag invalid cells with a comment
Combine comments with validation to explain data-entry errors. This example adds a comment to a cell when it fails validation and removes the comment once the value is valid. The [`afterValidate`](@/api/hooks.md#aftervalidate) hook drives the change, and [`setCommentAtCell()`](@/api/comments.md#setcommentatcell) writes the message. The grid validates on load, so the **Mechanical keyboard** row starts with an invalid stock value and shows its comment right away. Edit any **Stock** cell and enter a negative number or text to flag it, or enter a valid whole number to clear the flag.
::: only-for javascript
::: example #example5 --js 1 --ts 2
@[code](@/content/guides/cell-features/comments/javascript/example5.js)
@[code](@/content/guides/cell-features/comments/javascript/example5.ts)
:::
:::
::: only-for react
::: example #example5 :react --js 1 --ts 2
@[code](@/content/guides/cell-features/comments/react/example5.jsx)
@[code](@/content/guides/cell-features/comments/react/example5.tsx)
:::
:::
::: only-for angular
::: example #example5 :angular --ts 1 --html 2
@[code](@/content/guides/cell-features/comments/angular/example5.ts)
@[code](@/content/guides/cell-features/comments/angular/example5.html)
:::
:::
::: only-for vue
::: example #example5 :vue3
@[code](@/content/guides/cell-features/comments/vue/example5.vue)
:::
:::
## Read all comments programmatically
To collect every comment in the grid, loop through the rows and read each cell's metadata with [`getCellMetaAtRow()`](@/api/core.md#getcellmetaatrow). Each returned cell-meta object stores its comment under the `comment` key. Click **List all comments** to print all comments below the grid.
::: only-for javascript
::: example #example6 --html 1 --js 2 --ts 3
@[code](@/content/guides/cell-features/comments/javascript/example6.html)
@[code](@/content/guides/cell-features/comments/javascript/example6.js)
@[code](@/content/guides/cell-features/comments/javascript/example6.ts)
:::
:::
::: only-for react
::: example #example6 :react --js 1 --ts 2
@[code](@/content/guides/cell-features/comments/react/example6.jsx)
@[code](@/content/guides/cell-features/comments/react/example6.tsx)
:::
:::
::: only-for angular
::: example #example6 :angular --ts 1 --html 2
@[code](@/content/guides/cell-features/comments/angular/example6.ts)
@[code](@/content/guides/cell-features/comments/angular/example6.html)
:::
:::
::: only-for vue
::: example #example6 :vue3
@[code](@/content/guides/cell-features/comments/vue/example6.vue)
:::
:::
## Result
Cells with comments display a small indicator in the corner. Users can view, edit, or delete comments through the context menu, and pre-configured comments appear when the table loads.
## Related keyboard shortcuts
| Windows | macOS | Action | Excel | Sheets |
| ------------------------------------------------------- | ---------------------------------------------------------- | --------------------------------------- | :-----: | :-----: |
| **Ctrl**+**Alt**+**M** | ⌃+⌥+**M** | Add or edit a comment | ✗ | ✓ |
| **Ctrl**+**Enter** | ⌘+**Enter** | Save and exit the current comment | ✗ | ✓ |
| **Escape** | **Escape** | Exit the current comment without saving | ✗ | ✗ |
## Related API reference
**Configuration options**
- [commentedCellClassName](@/api/options.md#commentedcellclassname)
- [comments](@/api/options.md#comments)
**Hooks**
- [afterRemoveCellMeta](@/api/hooks.md#afterremovecellmeta)
- [afterSetCellMeta](@/api/hooks.md#aftersetcellmeta)
- [beforeRemoveCellMeta](@/api/hooks.md#beforeremovecellmeta)
- [beforeSetCellMeta](@/api/hooks.md#beforesetcellmeta)
**Plugins**
- [Comments](@/api/comments.md)
---
# Autofill values
URL: https://handsontable.com/docs/javascript-data-grid/autofill-values/
Copy a cell's value into multiple other cells, using the "fill handle" UI element. Configure the direction of copying, and more, through Handsontable's API.
[[toc]]
Autofill lets users drag the fill handle to copy or extend values across adjacent cells. Use it to speed up repetitive data entry.
## Autofill in all directions
Using the tiny square known as the 'fill handle' in the corner of the selected cell, you can drag it to repeat or extend values across adjacent cells.
You can also **double-click the fill handle** to autofill downward without dragging. Select the four cells from Symbol to Known Isotopes in the `Lithium` row (row header 3) and double-click the fill handle to see this in action.
### How double-click autofill determines the range
Handsontable scans the rows below your selection and fills down to the last row where the column immediately to the left or right of your selection contains a value. In the example below, the Name column acts as the guide -- the rows with row headers 4 and 5 have values there, so the fill extends through both rows.
Two conditions must be met for the fill to happen:
- **All cells below the selection in the filled column(s) must be empty.** If any cell below the selection in those columns contains data, double-clicking does nothing.
- At least one column adjacent to your selection must have data in the rows below.
**Visual difference from drag-fill:** When you drag the fill handle, a preview border shows the target range as you drag. When you double-click, no drag-preview appears -- the cells populate immediately based on the adjacent column data.
::: only-for javascript
::: example #example1 --js 1 --ts 2
@[code](@/content/guides/cell-features/autofill-values/javascript/example1.js)
@[code](@/content/guides/cell-features/autofill-values/javascript/example1.ts)
:::
:::
::: only-for react
::: example #example1 :react --js 1 --ts 2
@[code](@/content/guides/cell-features/autofill-values/react/example1.jsx)
@[code](@/content/guides/cell-features/autofill-values/react/example1.tsx)
:::
:::
::: only-for angular
::: example #example1 :angular --ts 1 --html 2
@[code](@/content/guides/cell-features/autofill-values/angular/example1.ts)
@[code](@/content/guides/cell-features/autofill-values/angular/example1.html)
:::
:::
::: only-for vue
::: example #example1 :vue3
@[code](@/content/guides/cell-features/autofill-values/vue/example1.vue)
:::
:::
## Autofill in a vertical direction only and creating new rows
In this configuration, the fill handle is restricted to move only vertically. New rows are automatically added to the bottom of the table by changing [`autoInsertRow`](@/api/options.md#fillhandle) to `true`.
::: only-for javascript
::: example #example2 --js 1 --ts 2
@[code](@/content/guides/cell-features/autofill-values/javascript/example2.js)
@[code](@/content/guides/cell-features/autofill-values/javascript/example2.ts)
:::
:::
::: only-for react
::: example #example2 :react --js 1 --ts 2
@[code](@/content/guides/cell-features/autofill-values/react/example2.jsx)
@[code](@/content/guides/cell-features/autofill-values/react/example2.tsx)
:::
:::
::: only-for angular
::: example #example2 :angular --ts 1 --html 2
@[code](@/content/guides/cell-features/autofill-values/angular/example2.ts)
@[code](@/content/guides/cell-features/autofill-values/angular/example2.html)
:::
:::
::: only-for vue
::: example #example2 :vue3
@[code](@/content/guides/cell-features/autofill-values/vue/example2.vue)
:::
:::
## Altering and tracking autofilled values
Use the [`beforeAutofill`](@/api/hooks.md#beforeautofill) hook to change the values Handsontable is about to fill in, and the [`afterAutofill`](@/api/hooks.md#afterautofill) hook to react once the fill completes.
In the example below, select the four cells from Symbol to Known Isotopes in the `Lithium` row and drag the fill handle down through the rows with row headers 4 and 5. The `afterAutofill` hook logs the affected range and direction to the output box below the grid. The logged row and column numbers are 0-based indices, so the two filled rows appear as `3-4`, not `4-5`.
The `beforeAutofill` hook in this example rounds every filled number up to the nearest multiple of 5. This is a demonstration of what the hook can do, not the default autofill result: without the hook, Handsontable repeats the selected values as they are.
When Handsontable fires [`beforeChange`](@/api/hooks.md#beforechange) or [`afterChange`](@/api/hooks.md#afterchange) as part of an autofill operation, their `source` argument is `Autofill.fill`. Read more about the `source` argument in [Events and hooks: Definition for `source` argument](@/guides/getting-started/events-and-hooks/events-and-hooks.md#definition-for-source-argument).
::: only-for javascript
::: example #example3 --html 1 --js 2 --ts 3
@[code](@/content/guides/cell-features/autofill-values/javascript/example3.html)
@[code](@/content/guides/cell-features/autofill-values/javascript/example3.js)
@[code](@/content/guides/cell-features/autofill-values/javascript/example3.ts)
:::
:::
::: only-for react
::: example #example3 :react --js 1 --ts 2
@[code](@/content/guides/cell-features/autofill-values/react/example3.jsx)
@[code](@/content/guides/cell-features/autofill-values/react/example3.tsx)
:::
:::
::: only-for angular
::: example #example3 :angular --ts 1 --html 2
@[code](@/content/guides/cell-features/autofill-values/angular/example3.ts)
@[code](@/content/guides/cell-features/autofill-values/angular/example3.html)
:::
:::
::: only-for vue
::: example #example3 :vue3
@[code](@/content/guides/cell-features/autofill-values/vue/example3.vue)
:::
:::
## Autofill and formulas
With the [`Formulas`](@/guides/formulas/formula-calculation/formula-calculation.md) plugin enabled, autofill delegates to the HyperFormula engine instead of copying cell contents literally:
- **Relative references adjust per target cell.** Filling `=A1+B1` down from row 1 to row 2 produces `=A2+B2` in the new cell, the same way a spreadsheet application adjusts formulas on fill.
- **Absolute references stay fixed.** Filling `=$A$1+B1` down keeps `$A$1` unchanged in every filled cell, while the relative `B1` part still adjusts.
- **The fill is cancelled** if the engine reports that the target cells can't be written to (for example, because they're part of another formula's dependency chain), leaving the target range unchanged.
## Result
The fill handle appears on the selected cell. Dragging it copies or extends values into adjacent cells in the configured direction.
## Related API reference
**Configuration options**
- [fillHandle](@/api/options.md#fillhandle)
**Hooks**
- [afterAutofill](@/api/hooks.md#afterautofill)
- [beforeAutofill](@/api/hooks.md#beforeautofill)
- [modifyAutofillRange](@/api/hooks.md#modifyautofillrange)
**Plugins**
- [Autofill](@/api/autofill.md)
---
# Formatting cells
URL: https://handsontable.com/docs/javascript-data-grid/formatting-cells/
Change the appearance of cells, using custom CSS classes, inline styles, or custom cell borders.
[[toc]]
## 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 [`className`](@/api/options.md#classname) when you want reusable static styles.
- Use [`renderer`](@/api/options.md#renderer) when you need to apply inline styles to specific cells at render time.
- Use [`customBorders`](@/api/options.md#customborders) when 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`](@/api/options.md#classname) option. Set it in the grid configuration, in a `columns` entry, or per cell through the [`cells`](@/api/options.md#cells) callback. The class you provide is added to the cell's `` element, where your CSS rules can target it.
::: only-for javascript
::: example #example1 --css 1 --js 2 --ts 3
@[code](@/content/guides/cell-features/formatting-cells/javascript/example1.css)
@[code](@/content/guides/cell-features/formatting-cells/javascript/example1.js)
@[code](@/content/guides/cell-features/formatting-cells/javascript/example1.ts)
:::
:::
::: only-for react
::: example #example1 :react --css 1 --js 2 --ts 3
@[code](@/content/guides/cell-features/formatting-cells/react/example1.css)
@[code](@/content/guides/cell-features/formatting-cells/react/example1.jsx)
@[code](@/content/guides/cell-features/formatting-cells/react/example1.tsx)
:::
:::
::: only-for angular
::: example #example1 :angular --ts 1 --html 2
@[code](@/content/guides/cell-features/formatting-cells/angular/example1.ts)
@[code](@/content/guides/cell-features/formatting-cells/angular/example1.html)
:::
:::
::: only-for vue
::: example #example1 :vue3
@[code](@/content/guides/cell-features/formatting-cells/vue/example1.vue)
:::
:::
### 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:
```css
.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`](@/api/options.md#autorowsize) and [`autoColumnSize`](@/api/options.md#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`](@/api/options.md#renderer) option to run this logic on each render.
::: only-for javascript
::: example #example2 --js 1 --ts 2
@[code](@/content/guides/cell-features/formatting-cells/javascript/example2.js)
@[code](@/content/guides/cell-features/formatting-cells/javascript/example2.ts)
:::
:::
::: only-for react
::: example #example2 :react --js 1 --ts 2
@[code](@/content/guides/cell-features/formatting-cells/react/example2.jsx)
@[code](@/content/guides/cell-features/formatting-cells/react/example2.tsx)
:::
:::
::: only-for angular
::: example #example2 :angular --ts 1 --html 2
@[code](@/content/guides/cell-features/formatting-cells/angular/example2.ts)
@[code](@/content/guides/cell-features/formatting-cells/angular/example2.html)
:::
:::
::: only-for vue
::: example #example2 :vue3
@[code](@/content/guides/cell-features/formatting-cells/vue/example2.vue)
:::
:::
## Custom cell borders
To enable custom borders, set [`customBorders`](@/api/options.md#customborders). You can set it to `true` or pass an array with predefined border configuration. For all settings and methods, see the [API reference](@/api/customBorders.md).
In API property names, `start` and `end` refer to the starting and ending edges of the [layout direction](@/guides/internationalization/layout-direction/layout-direction.md).
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.
::: only-for javascript
::: example #example3 --js 1 --ts 2
@[code](@/content/guides/cell-features/formatting-cells/javascript/example3.js)
@[code](@/content/guides/cell-features/formatting-cells/javascript/example3.ts)
:::
:::
::: only-for react
::: example #example3 :react --js 1 --ts 2
@[code](@/content/guides/cell-features/formatting-cells/react/example3.jsx)
@[code](@/content/guides/cell-features/formatting-cells/react/example3.tsx)
:::
:::
::: only-for angular
::: example #example3 :angular --ts 1 --html 2
@[code](@/content/guides/cell-features/formatting-cells/angular/example3.ts)
@[code](@/content/guides/cell-features/formatting-cells/angular/example3.html)
:::
:::
::: only-for vue
::: example #example3 :vue3
@[code](@/content/guides/cell-features/formatting-cells/vue/example3.vue)
:::
:::
### Apply borders progressively for large configurations
Building a very large [`customBorders`](@/api/options.md#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`](@/api/options.md#custombordersprogressive) to `true`:
```js
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:
```js
customBordersProgressive: { chunkSize: 5000 },
```
With progressive application enabled, the borders fill in after the grid becomes interactive, so [`getBorders()`](@/api/customBorders.md#getborders) and the cells' border metadata are complete only once the [`afterCustomBordersUpdate`](@/api/hooks.md#aftercustombordersupdate) hook fires:
```js
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**
- [Conditional formatting](@/guides/cell-features/conditional-formatting/conditional-formatting.md)
**Configuration options**
- [activeHeaderClassName](@/api/options.md#activeheaderclassname)
- [className](@/api/options.md#classname)
- [commentedCellClassName](@/api/options.md#commentedcellclassname)
- [currentColClassName](@/api/options.md#currentcolclassname)
- [currentHeaderClassName](@/api/options.md#currentheaderclassname)
- [currentRowClassName](@/api/options.md#currentrowclassname)
- [customBorders](@/api/options.md#customborders)
- [customBordersProgressive](@/api/options.md#custombordersprogressive)
- [invalidCellClassName](@/api/options.md#invalidcellclassname)
- [noWordWrapClassName](@/api/options.md#nowordwrapclassname)
- [placeholder](@/api/options.md#placeholder)
- [placeholderCellClassName](@/api/options.md#placeholdercellclassname)
- [readOnlyCellClassName](@/api/options.md#readonlycellclassname)
- [tableClassName](@/api/options.md#tableclassname)
**Plugins**
- [CustomBorders](@/api/customBorders.md)
---
# Cell functions
URL: https://handsontable.com/docs/javascript-data-grid/cell-function/
A cell function is one of three components -- renderer, editor, or validator -- that controls how a cell displays, accepts, and validates data.
[[toc]]
## Overview
Every Handsontable cell has three associated functions that handle distinct concerns:
| Function | Role | Implemented as |
| --- | --- | --- |
| [`renderer`](@/guides/cell-functions/cell-renderer/cell-renderer.md) | Controls how a cell looks: DOM structure, CSS classes, HTML content | A plain function |
| [`editor`](@/guides/cell-functions/cell-editor/cell-editor.md) | Controls how a cell is edited: input element, keyboard handling, open/close lifecycle | A class extending `BaseEditor` |
| [`validator`](@/guides/cell-functions/cell-validator/cell-validator.md) | Decides whether a cell value is acceptable | A function or `RegExp` |
The three functions are **independent**. You can mix and match any combination: use the built-in numeric editor with a custom renderer, override just the validator while keeping a built-in type, or write all three from scratch.
### Function signatures
```js
// renderer — called for every visible cell on every render
renderer(hotInstance, td, row, col, prop, value, cellProperties)
// hotInstance – Handsontable instance
// td – HTMLTableCellElement to modify
// row, col – visual row and column indexes
// prop – data property name (string) or column index (number)
// value – current cell value
// cellProperties – merged cell configuration object
// validator — may be synchronous or asynchronous
validator(value, callback)
// value – value to validate
// callback – call with true (valid) or false (invalid)
// RegExp alternative: /pattern/.test(value) must return true
// editor — a class; see the Cell editor guide for the full lifecycle API
class MyEditor extends BaseEditor { ... }
```
`validator` is **optional**. If no validator is defined for a cell, the cell is skipped entirely during validation — afterValidate will not fire for it, and it will not contribute to the validation cycle.
### `allowInvalid`
By default, `allowInvalid: true` — invalid cells are accepted into the data source but marked with the `htInvalid` CSS class. Set `allowInvalid: false` to reject invalid values and keep the editor open until a valid value is entered.
## Cell types bundle all three
A [cell type](@/guides/cell-types/cell-type/cell-type.md) is a preset that assigns a matching `renderer`, `editor`, and `validator` together under a single `type` alias. Using `type: 'numeric'` is shorthand for:
```js
{
renderer: Handsontable.renderers.NumericRenderer,
editor: Handsontable.editors.NumericEditor,
validator: Handsontable.validators.NumericValidator,
}
```
Built-in types: `text`, `numeric`, `checkbox`, `date`, `time`, `dropdown`, `autocomplete`, `password`, `handsontable`.
When you set an explicit `renderer`, `editor`, or `validator` alongside a `type`, the explicit function always takes precedence over the type for that function only:
```js
columns: [{
type: 'numeric', // sets NumericEditor + NumericValidator
renderer: myRenderer, // overrides only NumericRenderer; editor and validator stay numeric
}]
```
**When to use a type vs individual functions:**
- Use `type` when you want the standard, bundled behavior for a data kind (numbers, dates, checkboxes).
- Override a single function from a type when one aspect needs customizing but the rest is fine as-is.
- Set individual `renderer`/`editor`/`validator` directly when no built-in type fits or you need full control.
## Configuration priority
Cell functions resolve using the cascading configuration model. The most specific level wins:
```
cell[row][col] > column > global (root settings)
```
::: only-for javascript
```js
new Handsontable(container, {
type: 'text', // global fallback for all cells
columns: [
{ type: 'numeric' }, // overrides global for all cells in column 0
{ type: 'text' }, // same as global for column 1
],
cell: [
{ row: 0, col: 0, type: 'checkbox' }, // overrides column setting for cell [0, 0] only
],
});
```
:::
::: only-for react
```jsx
```
:::
::: only-for angular
```ts
settings: GridSettings = {
type: 'text', // global fallback for all cells
columns: [
{ type: 'numeric' }, // overrides global for all cells in column 0
{ type: 'text' }, // same as global for column 1
],
cell: [
{ row: 0, col: 0, type: 'checkbox' }, // overrides column setting for cell [0, 0] only
],
};
```
:::
::: only-for vue
```html
```
:::
## Mixing renderer, editor, and validator
The example below shows a product inventory table. Each column uses a different function configuration:
- **Product** — `type: 'text'` bundles text renderer, text editor, and no validator.
- **Price** — `type: 'numeric'` bundles numeric renderer (formatted as currency), numeric editor, and numeric validator.
- **Stock** — custom `renderer` (progress bar), built-in `'numeric'` editor, and a custom range `validator`. All three come from different sources.
::: only-for javascript
::: example #example1 --js 1 --ts 2 --css 3
@[code](@/content/guides/cell-functions/cell-function/javascript/example1.js)
@[code](@/content/guides/cell-functions/cell-function/javascript/example1.ts)
@[code](@/content/guides/cell-functions/cell-function/javascript/example1.css)
:::
:::
::: only-for react
::: example #example1 :react --css 1 --js 2 --ts 3
@[code](@/content/guides/cell-functions/cell-function/react/example1.css)
@[code](@/content/guides/cell-functions/cell-function/react/example1.jsx)
@[code](@/content/guides/cell-functions/cell-function/react/example1.tsx)
:::
:::
::: only-for angular
::: example #example1 :angular --ts 1 --html 2
@[code](@/content/guides/cell-functions/cell-function/angular/example1.ts)
@[code](@/content/guides/cell-functions/cell-function/angular/example1.html)
:::
:::
::: only-for vue
::: example #example1 :vue3
@[code](@/content/guides/cell-functions/cell-function/vue/example1.vue)
:::
:::
Double-click any **Stock** cell to edit it with the numeric editor. The bar renderer updates on save. Enter a value outside 0–1000 to see the validator reject it (cell turns red when `allowInvalid: false`).
## Performance
Renderers are called separately for every displayed cell on every table render. A table can render many times during its lifetime — after scrolling, sorting, editing, and more. Keep `renderer` functions as simple and fast as possible to avoid performance drops, especially with large datasets.
## Getting cell functions programmatically
Use [`getCellMeta(row, col)`](@/api/core.md#getcellmeta) to read all properties of a cell at once, or the dedicated getters for individual functions:
::: only-for javascript
```js
const cellProperties = hot.getCellMeta(0, 0);
cellProperties.renderer; // renderer function
cellProperties.editor; // editor class
cellProperties.validator; // validator function or RegExp
cellProperties.type; // cell type string
```
:::
::: only-for react
```jsx
const hot = hotRef.current.hotInstance;
const cellProperties = hot.getCellMeta(0, 0);
cellProperties.renderer; // renderer function
cellProperties.editor; // editor class
cellProperties.validator; // validator function or RegExp
cellProperties.type; // cell type string
```
:::
::: only-for angular
```ts
const cellProperties = this.hotTable.hotInstance.getCellMeta(0, 0);
const renderer = cellProperties.renderer; // renderer function
const editor = cellProperties.editor; // editor class
const validator = cellProperties.validator; // validator function or RegExp
const type = cellProperties.type; // cell type string
```
:::
::: only-for vue
```html
```
:::
Dedicated getters:
| Method | Returns |
| --- | --- |
| [`getCellRenderer(row, col)`](@/api/core.md#getcellrenderer) | The resolved renderer function for the cell |
| [`getCellEditor(row, col)`](@/api/core.md#getcelleditor) | The resolved editor class for the cell |
| [`getCellValidator(row, col)`](@/api/core.md#getcellvalidator) | The resolved validator function or `RegExp` for the cell |
If a cell's functions are defined through a cell type, the getters return the resolved functions, not the type string:
::: only-for javascript
```js
const hot = new Handsontable(container, {
columns: [{ type: 'numeric' }],
});
const cellProperties = hot.getCellMeta(0, 0);
cellProperties.renderer; // numericRenderer function
cellProperties.editor; // NumericEditor class
cellProperties.validator; // numericValidator function
cellProperties.type; // 'numeric'
```
:::
::: only-for react
::: tip
To call these methods, access the Handsontable instance through the `HotTable` ref's `hotInstance` property. See [Instance methods](@/guides/getting-started/react-methods/react-methods.md) for details.
:::
```jsx
const ExampleComponent = () => {
const hotRef = useRef(null);
useEffect(() => {
const hot = hotRef.current.hotInstance;
const cellProperties = hot.getCellMeta(0, 0);
cellProperties.renderer; // numericRenderer function
cellProperties.editor; // NumericEditor class
cellProperties.validator; // numericValidator function
cellProperties.type; // 'numeric'
});
return ;
};
```
:::
::: only-for angular
```ts
import { Component, AfterViewInit, ViewChild } from '@angular/core';
import { HotTableComponent } from '@handsontable/angular-wrapper';
@Component({
selector: 'app-example',
template: '',
standalone: false,
})
export class AppComponent implements AfterViewInit {
@ViewChild(HotTableComponent) hotTable!: HotTableComponent;
settings = {
columns: [{ type: 'numeric' }],
};
ngAfterViewInit() {
const hot = this.hotTable.hotInstance;
const cellProperties = hot.getCellMeta(0, 0);
const renderer = cellProperties.renderer; // numericRenderer function
const editor = cellProperties.editor; // NumericEditor class
const validator = cellProperties.validator; // numericValidator function
const type = cellProperties.type; // 'numeric'
}
}
```
:::
::: only-for vue
```html
```
:::
## Related articles
**Related guides**
- [Cell editor](@/guides/cell-functions/cell-editor/cell-editor.md)
- [Cell renderer](@/guides/cell-functions/cell-renderer/cell-renderer.md)
- [Cell validator](@/guides/cell-functions/cell-validator/cell-validator.md)
- [Cell type](@/guides/cell-types/cell-type/cell-type.md)
**Configuration options**
- Configuration options:
- [`editor`](@/api/options.md#editor)
- [`renderer`](@/api/options.md#renderer)
- [`type`](@/api/options.md#type)
- [`validator`](@/api/options.md#validator)
- [`allowInvalid`](@/api/options.md#allowinvalid)
- [`valueFormatter`](@/api/options.md#valueformatter)
- Core methods:
- [`destroyEditor()`](@/api/core.md#destroyeditor)
- [`getActiveEditor()`](@/api/core.md#getactiveeditor)
- [`getCellEditor()`](@/api/core.md#getcelleditor)
- [`getCellMeta()`](@/api/core.md#getcellmeta)
- [`getCellMetaAtRow()`](@/api/core.md#getcellmetaatrow)
- [`getCellsMeta()`](@/api/core.md#getcellsmeta)
- [`getCellRenderer()`](@/api/core.md#getcellrenderer)
- [`getCellValidator()`](@/api/core.md#getcellvalidator)
- [`setCellMeta()`](@/api/core.md#setcellmeta)
- [`setCellMetaObject()`](@/api/core.md#setcellmetaobject)
- [`removeCellMeta()`](@/api/core.md#removecellmeta)
- Hooks:
- [`afterBeginEditing`](@/api/hooks.md#afterbeginediting)
- [`afterGetCellMeta`](@/api/hooks.md#aftergetcellmeta)
- [`afterGetColumnHeaderRenderers`](@/api/hooks.md#aftergetcolumnheaderrenderers)
- [`afterGetRowHeaderRenderers`](@/api/hooks.md#aftergetrowheaderrenderers)
- [`afterValidate`](@/api/hooks.md#aftervalidate)
- [`afterRenderer`](@/api/hooks.md#afterrenderer)
- [`beforeGetCellMeta`](@/api/hooks.md#beforegetcellmeta)
- [`beforeRenderer`](@/api/hooks.md#beforerenderer)
- [`beforeValidate`](@/api/hooks.md#beforevalidate)
## Related
- [Cell renderer](@/guides/cell-functions/cell-renderer/cell-renderer.md) -- how to control cell display using renderer functions
- [Cell editor](@/guides/cell-functions/cell-editor/cell-editor.md) -- how to control cell editing using editor classes
- [Cell validator](@/guides/cell-functions/cell-validator/cell-validator.md) -- how to enforce data rules using validator functions
---
# Cell renderer
URL: https://handsontable.com/docs/javascript-data-grid/cell-renderer/
A cell renderer is a function that controls how cell content is displayed in the DOM. Override a built-in renderer or write your own to customize the visual output.
[[toc]]
## Use a cell renderer
You can use any of the built-in renderers by specifying their alias name in your column configuration. The example below shows how to use the `numeric` renderer, which formats numeric values according to the cell's formatting options:
::: only-for javascript
```js
const container = document.querySelector("#container");
const hot = new Handsontable(container, {
data: someData,
columns: [
{
renderer: "numeric",
},
],
});
```
:::
::: only-for react
```jsx
```
:::
::: only-for angular
```ts
settings = {
columns: [
{
renderer: "numeric",
},
]
};
```
:::
::: only-for vue
```html
```
:::
::: only-for react
## Declare a custom renderer as a component
Handsontable's React wrapper lets you create custom cell renderers using React components.
To use your component as a Handsontable renderer, pass it in the `renderer` prop if either `HotTable` or `HotColumn` components, as you would with any other config option.
::: tip
Handsontable's [`autoRowSize`](@/api/options.md#autorowsize) and [`autoColumnSize`](@/api/options.md#autocolumnsize) options require calculating the widths/heights of some of the cells before rendering them into the table. For this reason, it's not currently possible to use them alongside component-based renderers, as they're created after the table's initialization.
Be sure to turn those options off in your Handsontable configuration, as keeping them enabled may cause unexpected results. Please note that [`autoColumnSize`](@/api/options.md#autocolumnsize) is enabled by default.
:::
::: example #example1 :react --js 1 --ts 2
@[code](@/content/guides/cell-functions/cell-renderer/react/example1.jsx)
@[code](@/content/guides/cell-functions/cell-renderer/react/example1.tsx)
:::
## Use the renderer component within React's context
In this example, React's `Context` passes information available in the main app component to the renderer. In this case, we're using just the renderer, but the same principle works with [editors](@/guides/cell-functions/cell-editor/cell-editor.md) as well.
::: example #example2 :react --css 1 --js 2 --ts 3
@[code](@/content/guides/cell-functions/cell-renderer/react/example2.css)
@[code](@/content/guides/cell-functions/cell-renderer/react/example2.jsx)
@[code](@/content/guides/cell-functions/cell-renderer/react/example2.tsx)
:::
## Declare a custom renderer as a function
You can also declare a custom renderer for the `HotTable` component by declaring it as a function. In the simplest scenario, you can pass the rendering function as the `hotRenderer` prop into `HotTable` or `HotColumn`.
If you need the renderer to be a part of a `columns` config array, declare it under the `renderer` key.
The following example implements `@handsontable/react-wrapper` with a custom renderer added to one of the columns. It takes an image URL as the input and renders the image in the edited cell.
::: example #example3 :react --js 1 --ts 2
@[code](@/content/guides/cell-functions/cell-renderer/react/example3.jsx)
@[code](@/content/guides/cell-functions/cell-renderer/react/example3.tsx)
:::
:::
::: only-for angular
## Declare a custom renderer as a component
Handsontable's Angular wrapper lets you create custom cell renderers using Angular components. To do so, create a component that extends the base renderer class `HotCellRendererComponent`.
In the Angular wrapper for Handsontable, a renderer can be provided either as an Angular component or as a function. To use your component as a Handsontable renderer, pass it in the `renderer` prop to the `HotTableComponent`, as you would with any other config option.
::: tip
Handsontable's [`autoRowSize`](@/api/options.md#autorowsize) and [`autoColumnSize`](@/api/options.md#autocolumnsize) options require calculating the widths/heights of some of the cells before rendering them into the table. For this reason, it's not currently possible to use them alongside component-based renderers, as they're created after the table's initialization.
Be sure to turn those options off in your Handsontable configuration, as keeping them enabled may cause unexpected results. Please note that [`autoColumnSize`](@/api/options.md#autocolumnsize) is enabled by default.
:::
::: example #example1 :angular --ts 1 --html 2
@[code](@/content/guides/cell-functions/cell-renderer/angular/example1.ts)
@[code](@/content/guides/cell-functions/cell-renderer/angular/example1.html)
:::
You can create and use a custom cell renderer component that utilizes the `rendererProps` property and use them inside the renderer component.
::: example #example3 :angular --ts 1 --html 2
@[code](@/content/guides/cell-functions/cell-renderer/angular/example3.ts)
@[code](@/content/guides/cell-functions/cell-renderer/angular/example3.html)
:::
## Declare a custom renderer as an Angular template
The Angular wrapper supports using an Angular `TemplateRef` as a renderer. This is particularly useful if you want to leverage the power of Angular templates directly, without creating a full component.
::: example #example2 :angular --ts 1 --html 2
@[code](@/content/guides/cell-functions/cell-renderer/angular/example2.ts)
@[code](@/content/guides/cell-functions/cell-renderer/angular/example2.html)
:::
## Declare a custom renderer as a function
You can also declare a custom renderer for the `HotTable` component by declaring it as a function. In the simplest scenario, you can pass the rendering function as the `renderer` prop into `HotTableComponent`.
The following example implements `@handsontable/angular-wrapper` with a custom renderer added to one of the columns. It takes an image URL as the input and renders the image in the edited cell.
::: example #example4 :angular --ts 1 --html 2
@[code](@/content/guides/cell-functions/cell-renderer/angular/example4.ts)
@[code](@/content/guides/cell-functions/cell-renderer/angular/example4.html)
:::
:::
::: only-for vue
## Declare a custom renderer as a component
Handsontable's Vue wrapper lets you create custom cell renderers using Vue components.
To use a Vue component as a renderer, define the component with `defineComponent`, then write a renderer function that mounts it into the cell `td` element with Vue's `render` and `h` helpers. Mounting with `render(h(Component, props), td)` reuses the same component instance across re-renders -- Vue patches the existing tree instead of remounting it. To pass static props alongside the cell data, merge them into the second argument of `h()`.
::: tip
Handsontable's [`autoRowSize`](@/api/options.md#autorowsize) and [`autoColumnSize`](@/api/options.md#autocolumnsize) options require calculating the widths/heights of some of the cells before rendering them into the table. For this reason, it's not currently possible to use them alongside component-based renderers, as they're created after the table's initialization.
Be sure to turn those options off in your Handsontable configuration, as keeping them enabled may cause unexpected results. Please note that [`autoColumnSize`](@/api/options.md#autocolumnsize) is enabled by default.
:::
::: example #example1 :vue3
@[code](@/content/guides/cell-functions/cell-renderer/vue/example1.vue)
:::
If your component needs access to a Vue application context -- for example, global components, plugins, or `provide` / `inject` -- use `createApp(Component, props).mount(td)` instead of `render()`. Store a reference to the mounted app on the `td` element and call `app.unmount()` at the start of each render call to avoid leaking app instances:
::: example #example2 :vue3
@[code](@/content/guides/cell-functions/cell-renderer/vue/example2.vue)
:::
:::
::: only-for react angular
::: tip
All the sections below describe how to utilize the features available for the Handsontable function-based renderers.
:::
:::
## Register custom cell renderer
To register your own alias use `registerRenderer()` function from the `handsontable/renderers` module. It takes two arguments:
- `rendererName` - a string representing a renderer function
- `renderer` - a renderer function that will be represented by `rendererName`
If you'd like to register `asteriskDecoratorRenderer` under alias `asterisk` you have to call:
```js
import { registerRenderer } from "handsontable/renderers";
registerRenderer("asterisk", asteriskDecoratorRenderer);
```
::: only-for angular
::: tip
When using `registerRenderer()`, remember to call it at startup (e.g. in `main.ts` or `AppModule`), not in the component constructor. This ensures the renderer is registered before the table is initialized.
:::
:::
Choose aliases wisely. If you register your renderer under name that is already registered, the target function will be overwritten:
```js
import { registerRenderer } from "handsontable/renderers";
registerRenderer("text", asteriskDecoratorRenderer);
```
Now `"text"` alias points to `asteriskDecoratorRenderer` function, not the built-in `textRenderer`.
So, unless you intentionally want to overwrite an existing alias, try to choose a unique name. A good practice is prefixing your aliases with some custom name (for example your GitHub username) to minimize the possibility of name collisions. This is especially important if you want to publish your renderer, because you never know aliases has been registered by the user who uses your renderer.
```js
import { registerRenderer } from "handsontable/renderers";
registerRenderer("asterisk", asteriskDecoratorRenderer);
```
Someone might already registered such alias
```js
import { registerRenderer } from "handsontable/renderers";
registerRenderer("my.asterisk", asteriskDecoratorRenderer);
```
That's better.
## Use an alias
The final touch is to use registered aliases. That way users can easily refer to an alias without the need to know the name of the function.
To sum up, a well prepared renderer function should look like this:
```js
import { registerRenderer } from "handsontable/renderers";
function customRenderer(
hotInstance,
td,
row,
column,
prop,
value,
cellProperties
) {
// ...your custom logic of the renderer
}
// Register an alias
registerRenderer("my.custom", customRenderer);
```
From now on, you can use `customRenderer` like so:
::: only-for javascript
```js
const container = document.querySelector("#container");
const hot = new Handsontable(container, {
data: someData,
columns: [
{
renderer: "my.custom",
},
],
});
```
:::
::: only-for react
```jsx
```
:::
::: only-for angular
```ts
settings = {
columns: [
{
renderer: "my.custom",
},
]
};
```
:::
::: only-for vue
```html
```
:::
When your custom renderer should preserve the default text output, call the built-in `textRenderer()` first. See [Extend a built-in renderer](#extend-a-built-in-renderer).
## Extend a built-in renderer
When you build on top of a built-in renderer, Handsontable does not call that base renderer for you. You call it inside your custom renderer before your extra logic.
Use `textRenderer` as the base when you want plain-text output and then apply styling or additional DOM changes.
Use `htmlRenderer` as the base when your output is trusted HTML and you intentionally render with `innerHTML`.
Skip a base renderer when your renderer fully controls cell output from scratch, for example the image-based `coverRenderer` in [Render custom HTML in cells](#render-custom-html-in-cells).
Both of the following call styles are valid:
```js
// Legacy style, common in classic JavaScript examples.
textRenderer.apply(this, arguments);
// Direct invocation style, common in ESM and TypeScript examples.
textRenderer(instance, td, row, column, prop, value, cellProperties);
```
## Render custom HTML in cells
This example shows how to use custom cell renderers to display HTML content in a cell. This is a very powerful feature. Just remember to escape any HTML code that could be used for XSS attacks.
::: warning Security
Handsontable does not include a built-in HTML sanitizer. When rendering untrusted user HTML, you must supply your own sanitizer via the [`sanitizer`](@/api/options.md#sanitizer) option. Without it, rendering untrusted HTML creates XSS vulnerabilities. See [Security](@/guides/security/security/security.md) for details.
:::
In the below configuration:
- **Title** column uses built-in HTML renderer that allows any HTML. This is unsafe if your data comes from an untrusted source. A Handsontable user can enter `
```
:::
the functions [`editor`](@/api/options.md#editor), [`renderer`](@/api/options.md#renderer), and [`copyable`](@/api/options.md#copyable) are automatically set as follows:
::: only-for javascript
```js
columns: [{
editor: Handsontable.editors.PasswordEditor
renderer: Handsontable.renderers.PasswordRenderer,
copyable: false,
}]
```
:::
::: only-for react
```jsx
columns={[{
editor: Handsontable.editors.PasswordEditor
renderer: Handsontable.renderers.PasswordRenderer,
copyable: false,
}]}
```
:::
::: only-for angular
```ts
settings = {
columns: [
{
editor: Handsontable.editors.PasswordEditor
renderer: Handsontable.renderers.PasswordRenderer,
copyable: false,
},
],
};
```
:::
::: only-for vue
```vue
```
:::
Is an equivalent to defining them all:
::: only-for javascript
```js
columns: [
{
editor: false,
renderer: Handsontable.renderers.TextRenderer,
className: "my-cell",
readOnly: true,
myCustomProperty: "foo",
},
];
```
:::
::: only-for react
```jsx
columns={[{
editor: false,
renderer: Handsontable.renderers.TextRenderer,
className: 'my-cell',
readOnly: true,
myCustomProperty: 'foo',
}]}
```
:::
::: only-for angular
```ts
settings = {
columns: [
{
editor: false,
renderer: Handsontable.renderers.TextRenderer,
className: "my-cell",
readOnly: true,
myCustomProperty: "foo",
},
],
};
```
:::
::: only-for vue
```vue
```
:::
## Register custom cell type
When you create a custom cell type, best practice is to assign it as an alias that will refer to this particular type definition.
This gives users a convenient way of defining which cell type should be used for describing cell properties. The user doesn't need to know which part of the code is responsible for rendering, validating, or editing the cell value. They do not even need to know that there are any functions at all. You can change the cell behaviour associated with an alias without changing the code that defines a cell's properties.
To register your own alias use `Handsontable.cellTypes.registerCellType()` function. It takes two arguments:
- `cellTypeName` - a string representing the cell type object
- [`type`](@/api/options.md#type) - an object represented by `cellTypeName`. It can include cell behavior callbacks, such as [`editor`](@/api/options.md#editor), [`renderer`](@/api/options.md#renderer), and [`validator`](@/api/options.md#validator). It can also include Handsontable settings, such as [`readOnly`](@/api/options.md#readonly), [`className`](@/api/options.md#classname), and [`allowInvalid`](@/api/options.md#allowinvalid), or custom properties, such as `myCustomCellState`, available through `cellProperties`.
If you'd like to register `copyablePasswordType` under alias `copyable-password`, you need to call:
```js
Handsontable.cellTypes.registerCellType('copyable-password', {
editor: copyablePasswordEditor,
renderer: copyablePasswordRenderer,
});
```
Choose aliases wisely. If you register your cell type under name that is already registered, the target function will be overwritten:
```js
Handsontable.cellTypes.registerCellType('password', {
editor: copyablePasswordEditor,
renderer: copyablePasswordRenderer,
});
// Now 'password' alias points to the newly created
// object, not Handsontable.cellTypes.password
```
Unless you intentionally want to overwrite an existing alias, try to choose a unique name. Best practice is to prefix your aliases with a custom name to minimize the possibility of name collisions. This is especially important if you want to publish your cell type as you never know what aliases have been registered by a user who uses your cell type.
```js
Handsontable.cellTypes.registerCellType('copyable-password', {
editor: copyablePasswordEditor,
renderer: copyablePasswordRenderer,
});
```
Someone might already registered such alias. It would be better use a unique prefix:
```js
Handsontable.cellTypes.registerCellType('my.copyable-password', {
editor: copyablePasswordEditor,
renderer: copyablePasswordRenderer,
});
```
To sum up, a well-prepared cell type object should look like this:
```js
class MyEditor extends Handsontable.editors.TextEditor {}
function customRenderer(instance, td, row, column, prop, value, cellProperties) {
// ...renderer logic
}
function customValidator(query, callback) {
// ...validator logic
callback(/* Pass `true` or `false` */);
}
// Register an alias
Handsontable.cellTypes.registerCellType('my.custom', {
editor: MyEditor,
renderer: customRenderer,
validator: customValidator,
// You can add additional options to the cell type
// based on Handsontable settings
className: 'my-cell',
allowInvalid: true,
// Or you can add custom properties which
// will be accessible in `cellProperties`
myCustomCellState: 'complete',
});
```
## Using an alias
The next step is to use the registered aliases to enable users to easily refer to them without the need to know what the actual cell type object is. Here's an example of how you would use your cell definition:
::: only-for javascript
```js
const hot = new Handsontable(container, {
columns: [{
type: 'my.custom'
}]
});
```
:::
::: only-for react
```jsx
```
:::
::: only-for angular
```ts
settings = {
columns: [
{
type: "my.custom",
},
],
};
```
```html
```
:::
::: only-for vue
```vue
```
:::
## Precedence
It is possible to define the [`type`](@/api/options.md#type) option together with options such as [`renderer`](@/api/options.md#renderer), [`editor`](@/api/options.md#editor) or [`validator`](@/api/options.md#validator). For example:
::: only-for javascript
```js
const hot = new Handsontable(container, {
columns: [{
type: 'numeric',
// validator function defined elsewhere
validator: customValidator
}]
});
```
:::
::: only-for react
```jsx
```
:::
::: only-for angular
```ts
settings = {
columns: [
{
type: "numeric",
// validator function defined elsewhere
validator: customValidator,
},
],
};
```
```html
```
:::
::: only-for vue
```vue
```
:::
We defined the[`type`](@/api/options.md#type) for all cells in a column to be `numeric`. We also defined a validator function directly. In Handsontable, cell functions that are defined directly always take precedence over functions associated with cell type, so the above configuration is equivalent to:
::: only-for javascript
```js
const hot = new Handsontable(container, {
columns: [{
renderer: Handsontable.renderers.TextRenderer,
editor: Handsontable.editors.TextEditor,
validator: customValidator
}]
});
```
:::
::: only-for react
```jsx
```
:::
::: only-for angular
```ts
settings = {
columns: [
{
renderer: Handsontable.renderers.TextRenderer,
editor: Handsontable.editors.TextEditor,
validator: customValidator,
},
],
};
```
```html
```
:::
::: only-for vue
```vue
```
:::
There is one more way you can define the configuration using types:
::: only-for javascript
```js
const hot = new Handsontable(container, {
// validator function defined elsewhere
validator: customValidator,
columns: [{
type: 'my.custom'
}]
});
```
:::
::: only-for react
```jsx
```
:::
::: only-for angular
```ts
settings = {
validator: customValidator,
columns: [
{
renderer: Handsontable.renderers.TextRenderer,
editor: Handsontable.editors.TextEditor,
validator: customValidator,
},
],
};
```
```html
```
:::
::: only-for vue
```vue
```
:::
Using [cascade configuration](@/guides/getting-started/configuration-options/configuration-options.md#cascading-configuration) we define a table with two columns, with [`validator`](@/api/options.md#validator) set to `customValidator` function. The s[`type`](@/api/options.md#type) of the first column is set to `password`. The `Password` cell type does not define a validator function:
```js
{
renderer: Handsontable.renderers.PasswordRenderer,
editor: Handsontable.editors.PasswordEditor,
validator: undefined
}
```
Because `type: 'password'` is a more specific configuration for the cells in the first column than the `validator: customValidator`, cell functions associated with the `password` type take precedence over the functions defined on the higher level of configuration. Therefore, the equivalent configuration is:
::: only-for javascript
```js
function customValidator(query, callback) {
// ...validator logic
callback(/* Pass `true` or `false` */);
}
const hot = new Handsontable(container, {
columns: [{
renderer: Handsontable.renderers.PasswordRenderer,
editor: Handsontable.editors.PasswordEditor,
validator: undefined
}, {
renderer: Handsontable.renderers.TextRenderer,
editor: Handsontable.editors.TextEditor,
validator: customValidator
}]
});
```
:::
::: only-for react
```jsx
function customValidator(query, callback) {
// ...validator logic
callback(/* Pass `true` or `false` */);
}
;
```
:::
::: only-for angular
```ts
function customValidator(query, callback) {
// ...validator logic
callback(/* Pass `true` or `false` */);
}
settings = {
validator: customValidator,
columns: [
{
renderer: Handsontable.renderers.TextRenderer,
editor: Handsontable.editors.TextEditor,
validator: customValidator,
},
],
};
```
```html
```
:::
::: only-for vue
```vue
```
:::
## Built-in cell types example
The example below shows some of the built-in cell types, i.e. combinations of cell renderers and editors available in Handsontable. The example also shows the declaration of custom cell renderers, namely `yellowRenderer` and `greenRenderer`.
::: only-for javascript
::: example #example1 --js 1 --ts 2
@[code](@/content/guides/cell-types/cell-type/javascript/example1.js)
@[code](@/content/guides/cell-types/cell-type/javascript/example1.ts)
:::
:::
::: only-for react
::: example #example1 :react --js 1 --ts 2
@[code](@/content/guides/cell-types/cell-type/react/example1.jsx)
@[code](@/content/guides/cell-types/cell-type/react/example1.tsx)
:::
:::
::: only-for angular
::: example #example1 :angular --ts 1 --html 2
@[code](@/content/guides/cell-types/cell-type/angular/example1.ts)
@[code](@/content/guides/cell-types/cell-type/angular/example1.html)
:::
:::
::: only-for vue
::: example #example1 :vue3
@[code](@/content/guides/cell-types/cell-type/vue/example1.vue)
:::
:::
## Empty cells
It's worth to mention that values such as `''` (empty string), `null` and `undefined` are considered empty values. Cells with empty values are displayed in a similar way for most of the data types (see below).
::: tip
Please keep in mind that opening a cell with `undefined` and `null` values results in overwriting the original value with an empty string. Moreover, copying and pasting that values will result in pasting the empty string.
:::
::: only-for javascript
::: example #example2 --js 1 --ts 2
@[code](@/content/guides/cell-types/cell-type/javascript/example2.js)
@[code](@/content/guides/cell-types/cell-type/javascript/example2.ts)
:::
:::
::: only-for react
::: example #example2 :react --js 1 --ts 2
@[code](@/content/guides/cell-types/cell-type/react/example2.jsx)
@[code](@/content/guides/cell-types/cell-type/react/example2.tsx)
:::
:::
::: only-for angular
::: example #example2 :angular --ts 1 --html 2
@[code](@/content/guides/cell-types/cell-type/angular/example2.ts)
@[code](@/content/guides/cell-types/cell-type/angular/example2.html)
:::
:::
::: only-for vue
::: example #example2 :vue3
@[code](@/content/guides/cell-types/cell-type/vue/example2.vue)
:::
:::
Empty cells may be treated differently in different contexts, for example, the [`ColumnSorting`](@/api/columnSorting.md) plugin has `sortEmptyCells` option which is responsible for establishing whether empty cells should be sorted like non-empty cells.
## Set different cell types in one column
Use the [`cells`](@/api/options.md#cells) option to assign a different cell type to each row in a column. The following example creates a project settings form with `dropdown`, `numeric`, `intl-date`, `checkbox`, `text`, `handsontable`, and `password` cell types.
:::: only-for javascript
::: example #example3 --js 1 --ts 2
@[code](@/content/guides/cell-types/cell-type/javascript/example3.js)
@[code](@/content/guides/cell-types/cell-type/javascript/example3.ts)
:::
::::
:::: only-for react
::: example #example3 :react --js 1 --ts 2
@[code](@/content/guides/cell-types/cell-type/react/example3.jsx)
@[code](@/content/guides/cell-types/cell-type/react/example3.tsx)
:::
::::
:::: only-for angular
::: example #example3 :angular --ts 1 --html 2
@[code](@/content/guides/cell-types/cell-type/angular/example3.ts)
@[code](@/content/guides/cell-types/cell-type/angular/example3.html)
:::
::::
:::: only-for vue
::: example #example3 :vue3
@[code](@/content/guides/cell-types/cell-type/vue/example3.vue)
:::
::::
## Related
- [Autocomplete cell type](@/guides/cell-types/autocomplete-cell-type/autocomplete-cell-type.md)
- [Checkbox cell type](@/guides/cell-types/checkbox-cell-type/checkbox-cell-type.md)
- [Date cell type](@/guides/cell-types/date-cell-type/date-cell-type.md)
- [Dropdown cell type](@/guides/cell-types/dropdown-cell-type/dropdown-cell-type.md)
- [Handsontable cell type](@/guides/cell-types/handsontable-cell-type/handsontable-cell-type.md)
- [MultiSelect cell type](@/guides/cell-types/multiselect-cell-type/multiselect-cell-type.md)
- [Numeric cell type](@/guides/cell-types/numeric-cell-type/numeric-cell-type.md)
- [Password cell type](@/guides/cell-types/password-cell-type/password-cell-type.md)
- [Select cell type](@/guides/cell-types/select-cell-type/select-cell-type.md)
- [Text cell type](@/guides/cell-types/text-cell-type/text-cell-type.md)
- [Time cell type](@/guides/cell-types/time-cell-type/time-cell-type.md)
## Related articles
**Related guides**
- [Cell function](@/guides/cell-functions/cell-function/cell-function.md)
- [Cell editor](@/guides/cell-functions/cell-editor/cell-editor.md)
- [Cell renderer](@/guides/cell-functions/cell-renderer/cell-renderer.md)
- [Cell validator](@/guides/cell-functions/cell-validator/cell-validator.md)
**Configuration options**
- [editor](@/api/options.md#editor)
- [renderer](@/api/options.md#renderer)
- [type](@/api/options.md#type)
- [validator](@/api/options.md#validator)
**Core methods**
- [getCellEditor()](@/api/core.md#getcelleditor)
- [getCellMeta()](@/api/core.md#getcellmeta)
- [getCellMetaAtRow()](@/api/core.md#getcellmetaatrow)
- [getCellsMeta()](@/api/core.md#getcellsmeta)
- [getCellRenderer()](@/api/core.md#getcellrenderer)
- [getCellValidator()](@/api/core.md#getcellvalidator)
- [getDataType()](@/api/core.md#getdatatype)
- [setCellMeta()](@/api/core.md#setcellmeta)
- [setCellMetaObject()](@/api/core.md#setcellmetaobject)
- [removeCellMeta()](@/api/core.md#removecellmeta)
**Hooks**
- [afterBeginEditing](@/api/hooks.md#afterbeginediting)
- [afterGetCellMeta](@/api/hooks.md#aftergetcellmeta)
- [afterGetColumnHeaderRenderers](@/api/hooks.md#aftergetcolumnheaderrenderers)
- [afterGetRowHeaderRenderers](@/api/hooks.md#aftergetrowheaderrenderers)
- [afterSetCellMeta](@/api/hooks.md#aftersetcellmeta)
- [afterValidate](@/api/hooks.md#aftervalidate)
- [afterRenderer](@/api/hooks.md#afterrenderer)
- [beforeGetCellMeta](@/api/hooks.md#beforegetcellmeta)
- [beforeRenderer](@/api/hooks.md#beforerenderer)
- [beforeSetCellMeta](@/api/hooks.md#beforesetcellmeta)
- [beforeValidate](@/api/hooks.md#beforevalidate)
---
# Text cell type
URL: https://handsontable.com/docs/javascript-data-grid/text-cell-type/
Use the text cell type, the default cell type in Handsontable, to display and edit plain text values.
[[toc]]
## Overview
The text cell type is the default [cell type](@/guides/cell-types/cell-type/cell-type.md) in Handsontable. It renders a cell's value as plain text and lets you edit it with a standard text input. It has no built-in validator.
Because `text` is the default, you don't need to set `type: 'text'` for it to apply. Set it explicitly when you want to override a different type inherited from a higher configuration level, or when you want the configuration to state the type directly, for example to guard alphanumeric codes or values with leading zeros against being reformatted by another type.
::: only-for javascript
::: example #example1 --js 1 --ts 2
@[code](@/content/guides/cell-types/text-cell-type/javascript/example1.js)
@[code](@/content/guides/cell-types/text-cell-type/javascript/example1.ts)
:::
:::
::: only-for react
::: example #example1 :react --js 1 --ts 2
@[code](@/content/guides/cell-types/text-cell-type/react/example1.jsx)
@[code](@/content/guides/cell-types/text-cell-type/react/example1.tsx)
:::
:::
::: only-for angular
::: example #example1 :angular --ts 1 --html 2
@[code](@/content/guides/cell-types/text-cell-type/angular/example1.ts)
@[code](@/content/guides/cell-types/text-cell-type/angular/example1.html)
:::
:::
::: only-for vue
::: example #example1 :vue3
@[code](@/content/guides/cell-types/text-cell-type/vue/example1.vue)
:::
:::
In the example above, the `sku` and `category` columns use `type: 'text'` explicitly, even though it's the default. This makes the configuration self-documenting: values such as `'004821'` are alphanumeric codes with leading zeros, not numbers, and the explicit type states that intent even if a later change sets a different type at the grid or column level.
## Adding a validator
The text cell type ships without a built-in [validator](@/guides/cell-functions/cell-validator/cell-validator.md). To validate text values, combine `type: 'text'` with your own validator function, and set [`allowInvalid`](@/api/options.md#allowinvalid) to `false` to reject invalid entries.
With `allowInvalid: false`, the cell editor doesn't close on an invalid value. It stays open until you either enter a value that passes validation or press **Esc** to cancel the edit and restore the previous value. See [invalid cell commit semantics](@/guides/cell-functions/cell-validator/cell-validator.md#invalid-cell-commit-semantics-vs-visual-marking) for the full behavior, including how it differs from `allowInvalid: true`.
::: only-for javascript
::: example #example2 --js 1 --ts 2
@[code](@/content/guides/cell-types/text-cell-type/javascript/example2.js)
@[code](@/content/guides/cell-types/text-cell-type/javascript/example2.ts)
:::
:::
::: only-for react
::: example #example2 :react --js 1 --ts 2
@[code](@/content/guides/cell-types/text-cell-type/react/example2.jsx)
@[code](@/content/guides/cell-types/text-cell-type/react/example2.tsx)
:::
:::
::: only-for angular
::: example #example2 :angular --ts 1 --html 2
@[code](@/content/guides/cell-types/text-cell-type/angular/example2.ts)
@[code](@/content/guides/cell-types/text-cell-type/angular/example2.html)
:::
:::
::: only-for vue
::: example #example2 :vue3
@[code](@/content/guides/cell-types/text-cell-type/vue/example2.vue)
:::
:::
## Result
After configuring the text cell type, cells display and edit their value as plain text, with no formatting or masking applied. Combine it with a custom [`validator`](@/api/options.md#validator) to restrict which values a text cell accepts.
## Keyboard shortcuts
The text cell editor uses the standard [edition keyboard shortcuts](@/guides/navigation/keyboard-shortcuts/keyboard-shortcuts.md#edition-keyboard-shortcuts). It has no type-specific key bindings.
## Related articles
**Related guides**
- [Cell type](@/guides/cell-types/cell-type/cell-type.md)
- [Cell validator](@/guides/cell-functions/cell-validator/cell-validator.md)
**Configuration options**
- [type](@/api/options.md#type)
- [validator](@/api/options.md#validator)
- [allowInvalid](@/api/options.md#allowinvalid)
**Core methods**
- [getCellMeta()](@/api/core.md#getcellmeta)
- [getCellMetaAtRow()](@/api/core.md#getcellmetaatrow)
- [getCellsMeta()](@/api/core.md#getcellsmeta)
- [getDataType()](@/api/core.md#getdatatype)
- [setCellMeta()](@/api/core.md#setcellmeta)
- [setCellMetaObject()](@/api/core.md#setcellmetaobject)
- [removeCellMeta()](@/api/core.md#removecellmeta)
**Hooks**
- [afterGetCellMeta](@/api/hooks.md#aftergetcellmeta)
- [afterSetCellMeta](@/api/hooks.md#aftersetcellmeta)
- [afterValidate](@/api/hooks.md#aftervalidate)
- [beforeGetCellMeta](@/api/hooks.md#beforegetcellmeta)
- [beforeSetCellMeta](@/api/hooks.md#beforesetcellmeta)
- [beforeValidate](@/api/hooks.md#beforevalidate)
---
# Numeric cell type
URL: https://handsontable.com/docs/javascript-data-grid/numeric-cell-type/
Display, format, sort, and filter numbers correctly by using the numeric cell type.
The numeric cell type formats numbers using Intl.NumberFormat, right-aligns values, and validates input, marking non-numeric values as invalid.
[[toc]]
## Overview
The default cell type in Handsontable is text. The data of a text cell is processed as a `string`
type that corresponds to the value of the text editor's internal ` |