Handsontable: Spreadsheet-like JavaScript Data Grid Library

File Size: 19.5 MB
Views Total: 83948
Last Update:
Publish Date:
Official Website: Go to website
License: MIT
   
Handsontable: Spreadsheet-like JavaScript Data Grid Library

Handsontable is a spreadsheet-like JavaScript data grid library for editing structured data in the browser.

It's ideal for inventory tables, planning sheets, admin grids, financial models, and other data-heavy screens that need spreadsheet behavior.

The library currently runs independently of jQuery and also has wrappers for React, Angular, and Vue frameworks.

Features

  • Spreadsheet-style cell editing and keyboard navigation.
  • Row and column sorting, filtering, moving, resizing, hiding, and freezing.
  • Text, numeric, checkbox, date, time, date-time, select, dropdown, autocomplete, MultiSelect, password, and nested-grid cell types.
  • Custom renderers, editors, validators, and cell metadata.
  • Row pagination and virtualized rendering.
  • Nested headers, merged cells, comments, context menus, and dropdown menus.
  • Local array and object data plus server-driven data through DataProvider.
  • Main, Horizon, and Classic themes with light and dark color schemes.
  • CSV export plus XLSX export through ExcelJS.
  • Spreadsheet formulas through HyperFormula.
  • Hooks for edits, validation, selection, sorting, filtering, rendering, and data changes.

How To Use Handsontable

Install With npm

npm install handsontable

Import the library and a theme stylesheet in your application bundle.

import Handsontable from 'handsontable';
import 'handsontable/styles/ht-theme-main.min.css';

Load Handsontable From A CDN

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/handsontable/styles/ht-theme-main.min.css">
<script src="https://cdn.jsdelivr.net/npm/handsontable/dist/handsontable.full.min.js"></script>

Create The Grid Container

<div id="sales-grid"></div>

Render A Basic Spreadsheet Grid

Note that every current grid configuration needs a licenseKey. Use non-commercial-and-evaluation only for projects that qualify for Handsontable's non-commercial or evaluation terms. Commercial projects require a valid commercial license key.

var salesData = [
  ['Keyboard', 12400, 14350, true],
  ['Mouse', 9800, 11240, true],
  ['Monitor', 18300, 17650, false],
  ['Webcam', 7200, 8450, true]
];

var container = document.getElementById('sales-grid');
var hot = new Handsontable(container, {
  data: salesData,
  colHeaders: ['Product', 'Q1 Revenue', 'Q2 Revenue', 'Active'],
  rowHeaders: true,
  height: 320,
  stretchH: 'all',
  contextMenu: true,
  dropdownMenu: true,
  filters: true,
  columnSorting: true,
  theme: 'ht-theme-main',
  licenseKey: 'non-commercial-and-evaluation'
});

Configure Column Types

The columns setting maps object properties to spreadsheet columns and assigns cell behavior.

var inventory = [
  { sku: 'KB-101', product: 'Mechanical Keyboard', status: 'In stock', quantity: 42, featured: true },
  { sku: 'MS-205', product: 'Wireless Mouse', status: 'Low stock', quantity: 8, featured: false },
  { sku: 'MN-410', product: '27-inch Monitor', status: 'Backordered', quantity: 0, featured: true }
];

var hot = new Handsontable(document.getElementById('sales-grid'), {
  data: inventory,
  colHeaders: ['SKU', 'Product', 'Status', 'Quantity', 'Featured'],
  columns: [
    { data: 'sku', type: 'text', readOnly: true },
    { data: 'product', type: 'text' },
    { data: 'status', type: 'dropdown', source: ['In stock', 'Low stock', 'Backordered'] },
    { data: 'quantity', type: 'numeric', allowInvalid: false },
    { data: 'featured', type: 'checkbox' }
  ],
  rowHeaders: true,
  theme: 'ht-theme-main',
  licenseKey: 'non-commercial-and-evaluation'
});

Track And Save Cell Edits

The afterChange hook runs after an edit reaches the data model. Ignore the loadData source when initialization should not trigger persistence logic.

var hot = new Handsontable(document.getElementById('sales-grid'), {
  data: inventory,
  colHeaders: ['SKU', 'Product', 'Status', 'Quantity', 'Featured'],
  afterChange: function(changes, source) {
    if (!changes || source === 'loadData') {
      return;
    }

    // changes: [row, property/column, oldValue, newValue]
    console.log('Changed cells:', changes);
    console.log('Current source data:', hot.getSourceData());
  },
  theme: 'ht-theme-main',
  licenseKey: 'non-commercial-and-evaluation'
});

Use Server-Side Data

The dataProvider option connects the grid to backend data. A complete provider defines rowId, fetchRows, onRowsCreate, onRowsUpdate, and onRowsRemove. Handsontable passes pagination, sorting, and filter state to fetchRows, then sends row mutations through the corresponding callbacks.

Use DataProvider for datasets that should stay on the server. Client-side pagination loads the full dataset into the browser first.

Handsontable Options

Data And Structure

Option Description
data Sets local grid data as arrays or objects.
dataProvider Connects server-side row loading and CRUD callbacks.
columns Maps source fields to columns and defines per-column settings.
colHeaders Shows column headers or supplies custom header labels.
rowHeaders Shows row headers or supplies custom row labels.
nestedHeaders Creates multi-level column headers.
minSpareRows Keeps blank rows available after the current data.
dataSchema Defines the structure used when Handsontable creates new object rows.

Layout And Grid UI

Option Description
width Sets grid width.
height Sets grid height and controls the scrollable viewport.
stretchH Controls horizontal column stretching.
fixedRowsTop Freezes rows at the top edge.
fixedRowsBottom Freezes rows at the bottom edge.
fixedColumnsStart Freezes columns at the start edge.
manualColumnResize Turns on drag resizing for columns.
manualRowResize Turns on drag resizing for rows.
manualColumnMove Turns on drag reordering for columns.
manualRowMove Turns on drag reordering for rows.

Sorting, Filtering, Menus, And Navigation

Option Description
columnSorting Turns on single-column sorting.
multiColumnSorting Turns on sorting by multiple columns.
filters Turns on the Filters plugin.
dropdownMenu Shows a column dropdown menu.
contextMenu Shows the right-click context menu.
pagination Turns on row pagination or accepts pagination settings.
copyPaste Controls clipboard copy and paste behavior.
fillHandle Controls spreadsheet-style autofill from the selection handle.
selectionHandles Shows draggable handles for resizing selected ranges.
moveCells Turns on moving selected cells by dragging the selection border.

Cells And Editing

Option Description
readOnly Locks editing globally or for a configured cell.
cells Returns dynamic cell properties for each row and column.
type Selects a registered cell type.
editor Selects or disables a cell editor.
renderer Selects a cell renderer.
validator Runs custom validation logic.
allowInvalid Controls if invalid values stay in the grid.
source Supplies choices for autocomplete and dropdown editors.
strict Restricts autocomplete or dropdown values to the configured source.
className Applies CSS classes to configured cells.

Handsontable Methods

Method Purpose
getData() Returns rendered grid data in visual row and column order.
getSourceData() Returns a copy of the source data in physical order.
getDataAtCell(row, col) Reads the value at a visual row and column.
setDataAtCell(row, col, value) Writes a value to a cell through the grid API.
loadData(data) Replaces grid data and resets data-related state.
updateData(data) Replaces data while preserving row and column state where possible.
updateSettings(settings) Changes grid configuration after initialization.
getPlugin(name) Returns an initialized plugin instance such as filters or exportFile.
validateCells(callback) Runs configured cell validators across the current data set.
render() Requests a grid rerender after an external visual change.
destroy() Destroys the Handsontable instance and removes its DOM output.

Events And Hooks

Handsontable hooks run around grid lifecycle and user actions. They work as configuration callbacks or through the instance hook API.

Hook Runs When
beforeChange Cell changes are ready for review before Handsontable commits them.
afterChange Cell changes have reached the data model.
afterCreateRow After row creation.
afterRemoveRow After row removal.
afterColumnSort Column sorting has finished.
afterFilter Filter conditions have changed.
afterSelectionEnd A cell or range selection has finished.
afterValidate A configured cell validator has returned a result.
afterUpdateData The grid data has changed through updateData().
afterRender A render cycle has finished.

Register A Hook After Initialization

function logSelection(row, column, row2, column2) {
  console.log('Selected range:', row, column, row2, column2);
}

hot.addHook('afterSelectionEnd', logSelection);

// Remove the listener later.
hot.removeHook('afterSelectionEnd', logSelection);

Themes

Current Handsontable themes include main, horizon, and classic. Each theme supports light and dark color schemes. The Theme API also controls density and design tokens at runtime.

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/handsontable/styles/ht-theme-horizon.min.css">
var hot = new Handsontable(document.getElementById('sales-grid'), {
  data: salesData,
  theme: 'ht-theme-horizon',
  licenseKey: 'non-commercial-and-evaluation'
});

Row Pagination

Set pagination: true for client-side pagination. The grid keeps the full dataset in memory and shows one page at a time.

var hot = new Handsontable(document.getElementById('sales-grid'), {
  data: salesData,
  pagination: {
    pageSize: 20,
    pageSizeList: [10, 20, 50, 100],
    showPageSize: true,
    showCounter: true,
    showNavigation: true
  },
  theme: 'ht-theme-main',
  licenseKey: 'non-commercial-and-evaluation'
});

Formula Calculations With HyperFormula

The Formulas plugin uses HyperFormula for spreadsheet functions, references, and named expressions. Handsontable no longer bundles HyperFormula. Formula projects need the package as an explicit dependency.

npm install hyperformula
import Handsontable from 'handsontable';
import { HyperFormula } from 'hyperformula';
import 'handsontable/styles/ht-theme-main.min.css';

var hot = new Handsontable(document.getElementById('sales-grid'), {
  data: [
    ['Q1', 100],
    ['Q2', 150],
    ['Total', '=SUM(B1:B2)']
  ],
  formulas: {
    engine: HyperFormula
  },
  colHeaders: ['Period', 'Value'],
  theme: 'ht-theme-main',
  licenseKey: 'non-commercial-and-evaluation'
});

Export CSV And Excel Files

The ExportFile plugin exports CSV data directly in the browser.

var exportPlugin = hot.getPlugin('exportFile');

exportPlugin.downloadFile('csv', {
  filename: 'inventory_[YYYY]-[MM]-[DD]',
  colHeaders: true,
  rowHeaders: false,
  sanitizeValues: true
});

XLSX export uses ExcelJS as a peer dependency. Install exceljs, pass its constructor through exportFile.engines.xlsx, then use downloadFileAsync('xlsx', options).

npm install exceljs
import ExcelJS from 'exceljs';

var hot = new Handsontable(document.getElementById('sales-grid'), {
  data: inventory,
  exportFile: {
    engines: {
      xlsx: ExcelJS
    }
  },
  theme: 'ht-theme-main',
  licenseKey: 'non-commercial-and-evaluation'
});

var exportPlugin = hot.getPlugin('exportFile');
exportPlugin.downloadFileAsync('xlsx', {
  filename: 'inventory',
  colHeaders: true
});

Licensing And Browser Support

Current Handsontable releases use commercial licensing plus non-commercial and evaluation terms. The historical MIT release line ended with version 6.2.2. Review the license terms before deploying a current build in a commercial product.

Alternatives And Related Resources

FAQs

Q: Does Handsontable require jQuery?
A: No. Current Handsontable runs as a JavaScript data grid and has framework wrappers for React, Angular, and Vue. The jQuery wording in this page's historical URL reflects much older releases.

Q: Why does an old Handsontable example lose its styling after an upgrade?
A: Current releases use the theme system. Handsontable 17 removed the legacy handsontable.full.min.css file. Load a current theme stylesheet or configure the Theme API.

Q: Why does Handsontable show a license modal?
A: Current releases require a valid licenseKey. Handsontable 18.1 blocks the grid when the key is missing or invalid. Use non-commercial-and-evaluation for qualifying non-commercial and evaluation projects.

Q: How do I save edited Handsontable data?
A: Use afterChange to detect edits, then read the required values through getSourceData(), getData(), or cell-level methods. DataProvider mutation callbacks handle server-driven grids.

Q: Does Handsontable include spreadsheet formulas?
A: The Formulas plugin uses HyperFormula. Install HyperFormula as an explicit dependency for current Handsontable releases, then pass the class or an instance to formulas.engine.

Changelog

v18.1.0 (2026-09-01)

  • Selection handles for resizing ranges and cell movement by dragging a selection border.
  • Persian right-to-left language support.
  • Entitlement license key support and blocking behavior for missing or invalid keys.
  • Native date-time cell type plus color scheme and density settings.

v18.0.0 (2026-06-30)

  • Layout slots and a new layout option for grid UI elements.
  • Handsontable core source migrated to TypeScript.
  • HyperFormula became an explicit dependency for formula projects.
  • Handsontable removed legacy core undo/redo methods. Use the UndoRedo plugin API.

v17.1.0 (2026-05-19)

  • DataProvider for server-side row loading and mutations.
  • XLSX export through the ExportFile plugin.
  • Notification plugin and nested-header rowspan support.

v17.0.0 (2026-03-09)

  • Theme API and MultiSelect cell type.
  • Intl.NumberFormat and Intl.DateTimeFormat support.
  • Legacy stylesheet removal and deprecation cleanup.

This awesome jQuery plugin is developed by handsontable. For more Advanced Usages, please check the demo page or visit the official website.