Skip to content

Read-only cells

Make specified cells read-only to protect them from unwanted changes but still allow navigation and copying of data.

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 workCopy-paste works
Drag-to-fill doesn’t workDrag-to-fill works
Can’t be changed by populateFromArray()Can be changed by populateFromArray()

Make the grid read-only

To make the entire grid read-only, set readOnly to true as a top-level grid option.

TypeScript
/* file: app.component.ts */
import { Component } from '@angular/core';
import { GridSettings, HotTableModule } from '@handsontable/angular-wrapper';
@Component({
selector: 'example-readonly-grid',
standalone: true,
imports: [HotTableModule],
template: ` <div>
<hot-table [data]="data" [settings]="gridSettings"></hot-table>
</div>`,
})
export class AppComponent {
readonly data = [
{ car: 'Tesla', year: 2017, chassis: 'black', bumper: 'black' },
{ car: 'Nissan', year: 2018, chassis: 'blue', bumper: 'blue' },
{ car: 'Chrysler', year: 2019, chassis: 'yellow', bumper: 'black' },
{ car: 'Volvo', year: 2020, chassis: 'white', bumper: 'gray' },
];
readonly gridSettings: GridSettings = {
readOnly: true,
height: 'auto',
colHeaders: ['Car', 'Year', 'Chassis color', 'Bumper color'],
autoWrapRow: true,
autoWrapCol: true
};
}
/* end-file */
/* file: app.config.ts */
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { registerAllModules } from 'handsontable/registry';
import { HOT_GLOBAL_CONFIG, HotGlobalConfig, NON_COMMERCIAL_LICENSE } from '@handsontable/angular-wrapper';
// register Handsontable's modules
registerAllModules();
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
{
provide: HOT_GLOBAL_CONFIG,
useValue: { license: NON_COMMERCIAL_LICENSE } as HotGlobalConfig,
},
],
};
/* end-file */
HTML
<div>
<example-readonly-grid></example-readonly-grid>
</div>

Make a column read-only

To make a column read-only, declare it in the 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.

TypeScript
/* file: app.component.ts */
import { Component } from '@angular/core';
import { GridSettings, HotTableModule } from '@handsontable/angular-wrapper';
import { BaseRenderer } from 'handsontable/renderers';
import { textRenderer } from 'handsontable/renderers/textRenderer';
const dimmedTextRenderer: BaseRenderer = (instance, td, ...rest) => {
textRenderer(instance, td, ...rest);
td.style.opacity = '0.6';
};
@Component({
selector: 'example1-read-only-cells',
standalone: true,
imports: [HotTableModule],
template: ` <div>
<hot-table [data]="data" [settings]="gridSettings"></hot-table>
</div>`,
})
export class AppComponent {
readonly data = [
{ car: 'Tesla', year: 2017, chassis: 'black', bumper: 'black' },
{ car: 'Nissan', year: 2018, chassis: 'blue', bumper: 'blue' },
{ car: 'Chrysler', year: 2019, chassis: 'yellow', bumper: 'black' },
{ car: 'Volvo', year: 2020, chassis: 'white', bumper: 'gray' },
];
readonly gridSettings: GridSettings = {
height: 'auto',
colHeaders: ['Car', 'Year', 'Chassis color', 'Bumper color'],
autoWrapRow: true,
autoWrapCol: true,
columns: [
{
data: 'car',
readOnly: true,
renderer: dimmedTextRenderer,
},
{
data: 'year',
},
{
data: 'chassis',
},
{
data: 'bumper',
},
]
};
}
/* end-file */
/* file: app.config.ts */
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { registerAllModules } from 'handsontable/registry';
import { HOT_GLOBAL_CONFIG, HotGlobalConfig, NON_COMMERCIAL_LICENSE } from '@handsontable/angular-wrapper';
// register Handsontable's modules
registerAllModules();
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
{
provide: HOT_GLOBAL_CONFIG,
useValue: { license: NON_COMMERCIAL_LICENSE } as HotGlobalConfig,
},
],
};
/* end-file */
HTML
<div>
<example1-read-only-cells></example1-read-only-cells>
</div>

Make a row read-only

To make an entire row read-only, use the cells function and set the readOnly property based on the row index, regardless of the column. The example below makes the second row (index 1) read-only.

TypeScript
/* file: app.component.ts */
import { AfterViewInit, Component, ViewChild } from '@angular/core';
import { GridSettings, HotTableComponent, HotTableModule } from '@handsontable/angular-wrapper';
@Component({
selector: 'example5-read-only-cells',
standalone: true,
imports: [HotTableModule],
template: ` <div>
<hot-table [data]="data" [settings]="gridSettings"></hot-table>
</div>`,
})
export class AppComponent implements AfterViewInit {
@ViewChild(HotTableComponent, { static: false }) readonly hotTable!: HotTableComponent;
readonly data = [
{ car: 'Tesla', year: 2017, chassis: 'black', bumper: 'black' },
{ car: 'Nissan', year: 2018, chassis: 'blue', bumper: 'blue' },
{ car: 'Chrysler', year: 2019, chassis: 'yellow', bumper: 'black' },
{ car: 'Volvo', year: 2020, chassis: 'white', bumper: 'gray' },
];
readonly gridSettings: GridSettings ={
height: 'auto',
colHeaders: ['Car', 'Year', 'Chassis color', 'Bumper color'],
autoWrapRow: true,
autoWrapCol: true
};
ngAfterViewInit(): void {
const hot = this.hotTable?.hotInstance;
hot?.updateSettings({
cells: (row: number) => {
return row === 1 ? { readOnly: true } : {};
},
});
}
}
/* end-file */
/* file: app.config.ts */
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { registerAllModules } from 'handsontable/registry';
import { HOT_GLOBAL_CONFIG, HotGlobalConfig, NON_COMMERCIAL_LICENSE } from '@handsontable/angular-wrapper';
// register Handsontable's modules
registerAllModules();
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
{
provide: HOT_GLOBAL_CONFIG,
useValue: { license: NON_COMMERCIAL_LICENSE } as HotGlobalConfig,
},
],
};
/* end-file */
HTML
<div>
<example5-read-only-cells></example5-read-only-cells>
</div>

Make specific cells read-only

To make specific cells read-only, use the cells function to set the readOnly property conditionally. The example below makes cells that contain the word “Nissan” read-only.

TypeScript
/* file: app.component.ts */
import { AfterViewInit, Component, ViewChild } from '@angular/core';
import { GridSettings, HotTableComponent, HotTableModule } from '@handsontable/angular-wrapper';
@Component({
selector: 'example2-read-only-cells',
standalone: true,
imports: [HotTableModule],
template: ` <div>
<hot-table [data]="data" [settings]="gridSettings"></hot-table>
</div>`,
})
export class AppComponent implements AfterViewInit {
@ViewChild(HotTableComponent, { static: false }) readonly hotTable!: HotTableComponent;
readonly data = [
{ car: 'Tesla', year: 2017, chassis: 'black', bumper: 'black' },
{ car: 'Nissan', year: 2018, chassis: 'blue', bumper: 'blue' },
{ car: 'Chrysler', year: 2019, chassis: 'yellow', bumper: 'black' },
{ car: 'Volvo', year: 2020, chassis: 'white', bumper: 'gray' },
];
readonly gridSettings: GridSettings ={
height: 'auto',
colHeaders: ['Car', 'Year', 'Chassis color', 'Bumper color'],
autoWrapRow: true,
autoWrapCol: true
};
ngAfterViewInit(): void {
const hot = this.hotTable?.hotInstance;
hot?.updateSettings({
cells: (row: number, col: number, _: any) => {
if (hot.getData()[row][col] === 'Nissan') {
return { readOnly: true };
}
return {};
},
});
}
}
/* end-file */
/* file: app.config.ts */
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { registerAllModules } from 'handsontable/registry';
import { HOT_GLOBAL_CONFIG, HotGlobalConfig, NON_COMMERCIAL_LICENSE } from '@handsontable/angular-wrapper';
// register Handsontable's modules
registerAllModules();
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
{
provide: HOT_GLOBAL_CONFIG,
useValue: { license: NON_COMMERCIAL_LICENSE } as HotGlobalConfig,
},
],
};
/* end-file */
HTML
<div>
<example2-read-only-cells></example2-read-only-cells>
</div>

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 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.

TypeScript
/* file: app.component.ts */
import { Component } from '@angular/core';
import { GridSettings, HotTableModule } from '@handsontable/angular-wrapper';
import { BaseRenderer } from 'handsontable/renderers';
import { textRenderer } from 'handsontable/renderers/textRenderer';
const dimmedTextRenderer: BaseRenderer = (instance, td, ...rest) => {
textRenderer(instance, td, ...rest);
td.style.opacity = '0.6';
};
@Component({
selector: 'example3-read-only-cells',
standalone: true,
imports: [HotTableModule],
template: ` <div>
<hot-table [data]="data" [settings]="gridSettings"></hot-table>
</div>`,
})
export class AppComponent {
readonly data = [
{ car: 'Tesla', year: 2017, chassis: 'black', bumper: 'black' },
{ car: 'Nissan', year: 2018, chassis: 'blue', bumper: 'blue' },
{ car: 'Chrysler', year: 2019, chassis: 'yellow', bumper: 'black' },
{ car: 'Volvo', year: 2020, chassis: 'white', bumper: 'gray' },
];
readonly gridSettings: GridSettings = {
height: 'auto',
colHeaders: ['Car', 'Year', 'Chassis color', 'Bumper color'],
autoWrapRow: true,
autoWrapCol: true,
columns: [
{
data: 'car',
editor: false,
renderer: dimmedTextRenderer,
},
{
data: 'year',
editor: 'numeric',
},
{
data: 'chassis',
editor: 'text',
},
{
data: 'bumper',
editor: 'text',
},
]
};
}
/* end-file */
/* file: app.config.ts */
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { registerAllModules } from 'handsontable/registry';
import { HOT_GLOBAL_CONFIG, HotGlobalConfig, NON_COMMERCIAL_LICENSE } from '@handsontable/angular-wrapper';
// register Handsontable's modules
registerAllModules();
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
{
provide: HOT_GLOBAL_CONFIG,
useValue: { license: NON_COMMERCIAL_LICENSE } as HotGlobalConfig,
},
],
};
/* end-file */
HTML
<div>
<example3-read-only-cells></example3-read-only-cells>
</div>

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”.

TypeScript
/* file: app.component.ts */
import { AfterViewInit, Component, ViewChild } from '@angular/core';
import { GridSettings, HotTableComponent, HotTableModule } from '@handsontable/angular-wrapper';
@Component({
selector: 'example4-read-only-cells',
standalone: true,
imports: [HotTableModule],
template: ` <div>
<hot-table [data]="data" [settings]="gridSettings"></hot-table>
</div>`,
})
export class AppComponent implements AfterViewInit {
@ViewChild(HotTableComponent, { static: false }) readonly hotTable!: HotTableComponent;
readonly data = [
{ car: 'Tesla', year: 2017, chassis: 'black', bumper: 'black' },
{ car: 'Nissan', year: 2018, chassis: 'blue', bumper: 'blue' },
{ car: 'Chrysler', year: 2019, chassis: 'yellow', bumper: 'black' },
{ car: 'Volvo', year: 2020, chassis: 'white', bumper: 'gray' },
];
readonly gridSettings: GridSettings = {
height: 'auto',
colHeaders: ['Car', 'Year', 'Chassis color', 'Bumper color'],
autoWrapRow: true,
autoWrapCol: true
};
ngAfterViewInit(): void {
const hot = this.hotTable?.hotInstance;
hot?.updateSettings({
cells: (row, _col, prop) => {
if (hot.getDataAtRowProp(row, prop as string) === 'Nissan') {
return { editor: false };
}
return { editor: 'text' };
},
});
}
}
/* end-file */
/* file: app.config.ts */
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { registerAllModules } from 'handsontable/registry';
import { HOT_GLOBAL_CONFIG, HotGlobalConfig, NON_COMMERCIAL_LICENSE } from '@handsontable/angular-wrapper';
// register Handsontable's modules
registerAllModules();
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
{
provide: HOT_GLOBAL_CONFIG,
useValue: { license: NON_COMMERCIAL_LICENSE } as HotGlobalConfig,
},
],
};
/* end-file */
HTML
<div>
<example4-read-only-cells></example4-read-only-cells>
</div>

Accessibility

When 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.

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.

Configuration options