Skip to content

Rows sorting

Sort rows alphabetically or numerically, in ascending, descending, or a custom order, by one or multiple columns.

Overview

Handsontable provides two plugins for sorting rows:

  • ColumnSorting — sorts rows by a single column at a time. Clicking a column header cycles through ascending, descending, and unsorted states.
  • MultiColumnSorting — sorts rows by multiple columns simultaneously. Hold Ctrl/Cmd and click column headers to add more sort criteria.

Both plugins sort the view only. The source data array is never modified. To persist the sorted order back to the data source, see Saving data.

ColumnSorting and MultiColumnSorting are mutually exclusive. Enable only one at a time. If both options are set to true, ColumnSorting is automatically disabled.

Sorting demo

Click a column header to sort in ascending (↑) or descending (↓) order. Click again to return to the original order.

JavaScript
// to import sorting as an individual module, see the 'Import the sorting module' section of this page
import Handsontable from 'handsontable/base';
import { registerAllModules } from 'handsontable/registry';
// Register all Handsontable's modules.
registerAllModules();
const container = document.querySelector('#exampleSortingDemo');
new Handsontable(container, {
data: [
{
brand: 'Jetpulse',
model: 'Racing Socks',
price: 30,
sellDate: '2023-10-11',
sellTime: '01:23',
inStock: false,
},
{
brand: 'Gigabox',
model: 'HL Mountain Frame',
price: 1890.9,
sellDate: '2023-05-03',
sellTime: '11:27',
inStock: false,
},
{
brand: 'Camido',
model: 'Cycling Cap',
price: 130.1,
sellDate: '2023-03-27',
sellTime: '03:17',
inStock: true,
},
{
brand: 'Chatterpoint',
model: 'Road Tire Tube',
price: 59,
sellDate: '2023-08-28',
sellTime: '08:01',
inStock: true,
},
{
brand: 'Eidel',
model: 'HL Road Tire',
price: 279.99,
sellDate: '2023-10-02',
sellTime: '13:23',
inStock: true,
},
],
columns: [
{
title: 'Brand',
type: 'text',
data: 'brand',
},
{
title: 'Model',
type: 'text',
data: 'model',
},
{
title: 'Price',
type: 'numeric',
data: 'price',
locale: 'en-US',
numericFormat: {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2,
},
},
{
title: 'Date',
type: 'intl-date',
data: 'sellDate',
locale: 'en-US',
dateFormat: { month: 'short', day: 'numeric', year: 'numeric' },
className: 'htRight',
},
{
title: 'Time',
type: 'intl-time',
data: 'sellTime',
locale: 'en-US',
timeFormat: { hour: '2-digit', minute: '2-digit', hour12: true },
className: 'htRight',
},
{
title: 'In stock',
type: 'checkbox',
data: 'inStock',
className: 'htCenter',
},
],
// enable sorting for all columns
columnSorting: true,
height: 'auto',
stretchH: 'all',
autoWrapRow: true,
autoWrapCol: true,
licenseKey: 'non-commercial-and-evaluation',
});
TypeScript
// to import sorting as an individual module, see the 'Import the sorting module' section of this page
import Handsontable from 'handsontable/base';
import { registerAllModules } from 'handsontable/registry';
// Register all Handsontable's modules.
registerAllModules();
const container = document.querySelector('#exampleSortingDemo')!;
new Handsontable(container, {
data: [
{
brand: 'Jetpulse',
model: 'Racing Socks',
price: 30,
sellDate: '2023-10-11',
sellTime: '01:23',
inStock: false,
},
{
brand: 'Gigabox',
model: 'HL Mountain Frame',
price: 1890.9,
sellDate: '2023-05-03',
sellTime: '11:27',
inStock: false,
},
{
brand: 'Camido',
model: 'Cycling Cap',
price: 130.1,
sellDate: '2023-03-27',
sellTime: '03:17',
inStock: true,
},
{
brand: 'Chatterpoint',
model: 'Road Tire Tube',
price: 59,
sellDate: '2023-08-28',
sellTime: '08:01',
inStock: true,
},
{
brand: 'Eidel',
model: 'HL Road Tire',
price: 279.99,
sellDate: '2023-10-02',
sellTime: '13:23',
inStock: true,
},
],
columns: [
{
title: 'Brand',
type: 'text',
data: 'brand',
},
{
title: 'Model',
type: 'text',
data: 'model',
},
{
title: 'Price',
type: 'numeric',
data: 'price',
locale: 'en-US',
numericFormat: {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2,
},
},
{
title: 'Date',
type: 'intl-date',
data: 'sellDate',
locale: 'en-US',
dateFormat: { month: 'short', day: 'numeric', year: 'numeric' },
className: 'htRight',
},
{
title: 'Time',
type: 'intl-time',
data: 'sellTime',
locale: 'en-US',
timeFormat: { hour: '2-digit', minute: '2-digit', hour12: true },
className: 'htRight',
},
{
title: 'In stock',
type: 'checkbox',
data: 'inStock',
className: 'htCenter',
},
],
// enable sorting for all columns
columnSorting: true,
height: 'auto',
stretchH: 'all',
autoWrapRow: true,
autoWrapCol: true,
licenseKey: 'non-commercial-and-evaluation',
});
HTML
<div id="exampleSortingDemo"></div>

Enable sorting

To enable sorting for all columns, set columnSorting to true.

const configurationOptions = {
columnSorting: true,
};

To disable sorting for specific columns, set headerAction to false in the per-column configuration. In the following example, only the Model, Date, and In stock columns are sortable.

JavaScript
import Handsontable from 'handsontable/base';
import { registerAllModules } from 'handsontable/registry';
// Register all Handsontable's modules.
registerAllModules();
const container = document.querySelector('#exampleEnableSortingForColumns');
new Handsontable(container, {
data: [
{
brand: 'Jetpulse',
model: 'Racing Socks',
price: 30,
sellDate: '2023-10-11',
sellTime: '01:23',
inStock: false,
},
{
brand: 'Gigabox',
model: 'HL Mountain Frame',
price: 1890.9,
sellDate: '2023-05-03',
sellTime: '11:27',
inStock: false,
},
{
brand: 'Camido',
model: 'Cycling Cap',
price: 130.1,
sellDate: '2023-03-27',
sellTime: '03:17',
inStock: true,
},
{
brand: 'Chatterpoint',
model: 'Road Tire Tube',
price: 59,
sellDate: '2023-08-28',
sellTime: '08:01',
inStock: true,
},
{
brand: 'Eidel',
model: 'HL Road Tire',
price: 279.99,
sellDate: '2023-10-02',
sellTime: '13:23',
inStock: true,
},
],
// enable sorting for all columns
columnSorting: true,
columns: [
{
title: 'Brand',
type: 'text',
data: 'brand',
// disable sorting for the 'Brand' column
columnSorting: {
headerAction: false,
},
},
{
title: 'Model',
type: 'text',
data: 'model',
},
{
title: 'Price',
type: 'numeric',
data: 'price',
locale: 'en-US',
numericFormat: {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2,
},
// disable sorting for the 'Price' column
columnSorting: {
headerAction: false,
},
},
{
title: 'Date',
type: 'intl-date',
data: 'sellDate',
locale: 'en-US',
dateFormat: { month: 'short', day: 'numeric', year: 'numeric' },
className: 'htRight',
},
{
title: 'Time',
type: 'intl-time',
data: 'sellTime',
locale: 'en-US',
timeFormat: { hour: '2-digit', minute: '2-digit', hour12: true },
className: 'htRight',
// disable sorting for the 'Time' column
columnSorting: {
headerAction: false,
},
},
{
title: 'In stock',
type: 'checkbox',
data: 'inStock',
className: 'htCenter',
},
],
height: 'auto',
stretchH: 'all',
autoWrapRow: true,
autoWrapCol: true,
licenseKey: 'non-commercial-and-evaluation',
});
TypeScript
import Handsontable from 'handsontable/base';
import { registerAllModules } from 'handsontable/registry';
// Register all Handsontable's modules.
registerAllModules();
const container = document.querySelector('#exampleEnableSortingForColumns')!;
new Handsontable(container, {
data: [
{
brand: 'Jetpulse',
model: 'Racing Socks',
price: 30,
sellDate: '2023-10-11',
sellTime: '01:23',
inStock: false,
},
{
brand: 'Gigabox',
model: 'HL Mountain Frame',
price: 1890.9,
sellDate: '2023-05-03',
sellTime: '11:27',
inStock: false,
},
{
brand: 'Camido',
model: 'Cycling Cap',
price: 130.1,
sellDate: '2023-03-27',
sellTime: '03:17',
inStock: true,
},
{
brand: 'Chatterpoint',
model: 'Road Tire Tube',
price: 59,
sellDate: '2023-08-28',
sellTime: '08:01',
inStock: true,
},
{
brand: 'Eidel',
model: 'HL Road Tire',
price: 279.99,
sellDate: '2023-10-02',
sellTime: '13:23',
inStock: true,
},
],
// enable sorting for all columns
columnSorting: true,
columns: [
{
title: 'Brand',
type: 'text',
data: 'brand',
// disable sorting for the 'Brand' column
columnSorting: {
headerAction: false,
},
},
{
title: 'Model',
type: 'text',
data: 'model',
},
{
title: 'Price',
type: 'numeric',
data: 'price',
locale: 'en-US',
numericFormat: {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2,
},
// disable sorting for the 'Price' column
columnSorting: {
headerAction: false,
},
},
{
title: 'Date',
type: 'intl-date',
data: 'sellDate',
locale: 'en-US',
dateFormat: { month: 'short', day: 'numeric', year: 'numeric' },
className: 'htRight',
},
{
title: 'Time',
type: 'intl-time',
data: 'sellTime',
locale: 'en-US',
timeFormat: { hour: '2-digit', minute: '2-digit', hour12: true },
className: 'htRight',
// disable sorting for the 'Time' column
columnSorting: {
headerAction: false,
},
},
{
title: 'In stock',
type: 'checkbox',
data: 'inStock',
className: 'htCenter',
},
],
height: 'auto',
stretchH: 'all',
autoWrapRow: true,
autoWrapCol: true,
licenseKey: 'non-commercial-and-evaluation',
});
HTML
<div id="exampleEnableSortingForColumns"></div>

Configure sorting

Set columnSorting to an object to configure the plugin. The available options are:

OptionTypeDefaultDescription
headerActionbooleantrueWhen true, clicking a column header sorts by that column.
sortEmptyCellsbooleanfalseWhen true, empty cells participate in sorting. When false, empty cells are always placed at the end.
indicatorbooleantrueWhen true, a sort-order arrow icon is shown in the column header.
compareFunctionFactoryfunctionA factory that returns a custom comparator function. See Add a custom comparator.
initialConfigobjectSort config applied at initialization. Contains column (visual index) and sortOrder ('asc' or 'desc').
const configurationOptions = {
columnSorting: {
// enable click-to-sort on column headers (default: true)
headerAction: true,
// place empty cells at the end (default: false)
sortEmptyCells: false,
// show sort-order arrow in the column header (default: true)
indicator: true,
// sort column 1 descending at initialization
initialConfig: {
column: 1,
sortOrder: 'desc',
},
compareFunctionFactory(sortOrder, columnMeta) {
return function(value, nextValue) {
// return -1, 0, or 1
};
},
},
};

You can also override columnSorting options per column, using the columns configuration:

const configurationOptions = {
columnSorting: true,
columns: [
{
columnSorting: {
// no sort icon for the first column
indicator: false,
// disable click-to-sort for the first column
headerAction: false,
},
},
],
};

Sort different types of data

Handsontable applies type-aware sorting automatically when you set the type option on a column. The supported cell types are:

You can also define a custom cell type. See Cell type.

JavaScript
import Handsontable from 'handsontable/base';
import { registerAllModules } from 'handsontable/registry';
// Register all Handsontable's modules.
registerAllModules();
const container = document.querySelector('#exampleSortDifferentTypes');
new Handsontable(container, {
data: [
{
model: 'Racing Socks',
size: 'S',
price: 30,
sellDate: '2023-10-11',
sellTime: '01:23',
inStock: false,
color: 'Black',
email: '8576@all.xyz',
},
{
model: 'HL Mountain Shirt',
size: 'XS',
price: 1890.9,
sellDate: '2023-05-03',
sellTime: '11:27',
inStock: false,
color: 'White',
email: 'tayn@all.xyz',
},
{
model: 'Cycling Cap',
size: 'L',
price: 130.1,
sellDate: '2023-03-27',
sellTime: '03:17',
inStock: true,
color: 'Green',
email: '6lights@far.com',
},
{
model: 'Ski Jacket',
size: 'M',
price: 59,
sellDate: '2023-08-28',
sellTime: '08:01',
inStock: true,
color: 'Blue',
email: 'raj@fq1my2c.com',
},
{
model: 'HL Goggles',
size: 'XL',
price: 279.99,
sellDate: '2023-10-02',
sellTime: '13:23',
inStock: true,
color: 'Black',
email: 'da@pdc.ga',
},
],
columns: [
{
title: 'Model<br>(text)',
// set the type of the 'Model' column
type: 'text',
data: 'model',
},
{
title: 'Price<br>(numeric)',
// set the type of the 'Price' column
type: 'numeric',
data: 'price',
locale: 'en-US',
numericFormat: {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2,
},
},
{
title: 'Sold on<br>(date)',
// set the type of the 'Date' column
type: 'intl-date',
data: 'sellDate',
locale: 'en-US',
dateFormat: { month: 'short', day: 'numeric', year: 'numeric' },
className: 'htRight',
},
{
title: 'Time<br>(time)',
// set the type of the 'Time' column
type: 'intl-time',
data: 'sellTime',
locale: 'en-US',
timeFormat: { hour: '2-digit', minute: '2-digit', hour12: true },
className: 'htRight',
},
{
title: 'In stock<br>(checkbox)',
// set the type of the 'In stock' column
type: 'checkbox',
data: 'inStock',
className: 'htCenter',
},
{
title: 'Size<br>(dropdown)',
// set the type of the 'Size' column
type: 'dropdown',
data: 'size',
source: ['XS', 'S', 'M', 'L', 'XL'],
className: 'htCenter',
},
{
title: 'Color<br>(autocomplete)',
// set the type of the 'Size' column
type: 'autocomplete',
data: 'color',
source: ['White', 'Black', 'Yellow', 'Blue', 'Green'],
className: 'htCenter',
},
{
title: 'Email<br>(password)',
// set the type of the 'Email' column
type: 'password',
data: 'email',
},
],
columnSorting: true,
height: 'auto',
stretchH: 'all',
autoWrapRow: true,
autoWrapCol: true,
licenseKey: 'non-commercial-and-evaluation',
});
TypeScript
import Handsontable from 'handsontable/base';
import { registerAllModules } from 'handsontable/registry';
// Register all Handsontable's modules.
registerAllModules();
const container = document.querySelector('#exampleSortDifferentTypes')!;
new Handsontable(container, {
data: [
{
model: 'Racing Socks',
size: 'S',
price: 30,
sellDate: '2023-10-11',
sellTime: '01:23',
inStock: false,
color: 'Black',
email: '8576@all.xyz',
},
{
model: 'HL Mountain Shirt',
size: 'XS',
price: 1890.9,
sellDate: '2023-05-03',
sellTime: '11:27',
inStock: false,
color: 'White',
email: 'tayn@all.xyz',
},
{
model: 'Cycling Cap',
size: 'L',
price: 130.1,
sellDate: '2023-03-27',
sellTime: '03:17',
inStock: true,
color: 'Green',
email: '6lights@far.com',
},
{
model: 'Ski Jacket',
size: 'M',
price: 59,
sellDate: '2023-08-28',
sellTime: '08:01',
inStock: true,
color: 'Blue',
email: 'raj@fq1my2c.com',
},
{
model: 'HL Goggles',
size: 'XL',
price: 279.99,
sellDate: '2023-10-02',
sellTime: '13:23',
inStock: true,
color: 'Black',
email: 'da@pdc.ga',
},
],
columns: [
{
title: 'Model<br>(text)',
// set the type of the 'Model' column
type: 'text', // 'text' is the default type, so you can omit this line
data: 'model',
},
{
title: 'Price<br>(numeric)',
// set the type of the 'Price' column
type: 'numeric',
data: 'price',
locale: 'en-US',
numericFormat: {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2,
},
},
{
title: 'Sold on<br>(date)',
// set the type of the 'Date' column
type: 'intl-date',
data: 'sellDate',
locale: 'en-US',
dateFormat: { month: 'short', day: 'numeric', year: 'numeric' },
className: 'htRight',
},
{
title: 'Time<br>(time)',
// set the type of the 'Time' column
type: 'intl-time',
data: 'sellTime',
locale: 'en-US',
timeFormat: { hour: '2-digit', minute: '2-digit', hour12: true },
className: 'htRight',
},
{
title: 'In stock<br>(checkbox)',
// set the type of the 'In stock' column
type: 'checkbox',
data: 'inStock',
className: 'htCenter',
},
{
title: 'Size<br>(dropdown)',
// set the type of the 'Size' column
type: 'dropdown',
data: 'size',
source: ['XS', 'S', 'M', 'L', 'XL'],
className: 'htCenter',
},
{
title: 'Color<br>(autocomplete)',
// set the type of the 'Size' column
type: 'autocomplete',
data: 'color',
source: ['White', 'Black', 'Yellow', 'Blue', 'Green'],
className: 'htCenter',
},
{
title: 'Email<br>(password)',
// set the type of the 'Email' column
type: 'password',
data: 'email',
},
],
columnSorting: true,
height: 'auto',
stretchH: 'all',
autoWrapRow: true,
autoWrapCol: true,
licenseKey: 'non-commercial-and-evaluation',
});
HTML
<div id="exampleSortDifferentTypes"></div>

Set an initial sort order

Use the initialConfig option to apply a sort order when Handsontable initializes. column is the visual column index. sortOrder is 'asc' for ascending or 'desc' for descending.

const configurationOptions = {
columnSorting: {
initialConfig: {
column: 0,
sortOrder: 'asc',
},
},
};

To set an initial sort order across multiple columns, use the MultiColumnSorting plugin with an array value for initialConfig. See Set an initial multi-column sort order.

Add a custom comparator

A comparator is a function that determines sort order based on two cell values. Use a custom comparator to implement sorting logic beyond Handsontable’s built-in defaults.

Common use cases:

  • Sort by value length, occurrence of a character, or any other custom criterion.
  • Exclude specific rows from sorting (for example, rows with a particular job title).

Use the compareFunctionFactory option to provide a comparator factory. The factory receives sortOrder ('asc' or 'desc') and columnMeta, and must return a comparator function. The comparator receives two cell values and must return -1, 0, or 1.

const configurationOptions = {
columnSorting: {
compareFunctionFactory: function(sortOrder, columnMeta) {
return function(value, nextValue) {
if (value < nextValue) return -1;
if (value > nextValue) return 1;
return 0;
};
},
},
};

Use sorting hooks

Run code before or after sorting using the following Handsontable hooks:

  • beforeColumnSort — fires before sorting. Return false to cancel the sort and keep the current order.
  • afterColumnSort — fires after sorting completes.

A common use of beforeColumnSort is server-side sorting: cancel the client-side sort, send the sort configuration to a server, and reload the data. A common use of afterColumnSort is excluding specific rows from the sorted result.

const configurationOptions = {
beforeColumnSort(currentSortConfig, destinationSortConfigs) {
// add your code here
return false; // return false to block front-end sorting
},
afterColumnSort(currentSortConfig, sortedSortConfigs) {
// add your code here
},
};

Exclude rows from sorting

You can prevent specific top or bottom rows from being sorted. This is useful when a frozen row at the top displays column labels, or a frozen row at the bottom displays column summaries — rows that should always stay in place regardless of the sort order.

JavaScript
import Handsontable from 'handsontable/base';
import { registerAllModules } from 'handsontable/registry';
// Register all Handsontable's modules.
registerAllModules();
const container = document.querySelector('#exampleExcludeRowsFromSorting');
const hot = new Handsontable(container, {
data: [
{
brand: 'Brand',
model: 'Model',
price: 'Price',
sellDate: 'Date',
sellTime: 'Time',
inStock: 'In stock',
},
{
brand: 'Gigabox',
model: 'HL Mountain Frame',
price: 1890.9,
sellDate: '2023-05-03',
sellTime: '11:27',
inStock: 11,
},
{
brand: 'Camido',
model: 'Cycling Cap',
price: 130.1,
sellDate: '2023-03-27',
sellTime: '03:17',
inStock: 0,
},
{
brand: 'Chatterpoint',
model: 'Road Tire Tube',
price: 59,
sellDate: '2023-08-28',
sellTime: '08:01',
inStock: 1,
},
{
brand: 'Eidel',
model: 'HL Road Tire',
price: 279.99,
sellDate: '2023-10-02',
sellTime: '13:23',
inStock: 3,
},
{
brand: 'Jetpulse',
model: 'Racing Socks',
price: 30,
sellDate: '2023-10-11',
sellTime: '01:23',
inStock: 5,
},
{
brand: 'Gigabox',
model: 'HL Mountain Frame',
price: 1890.9,
sellDate: '2023-05-03',
sellTime: '11:27',
inStock: 22,
},
{
brand: 'Camido',
model: 'Cycling Cap',
price: 130.1,
sellDate: '2023-03-27',
sellTime: '03:17',
inStock: 13,
},
{
brand: 'Chatterpoint',
model: 'Road Tire Tube',
price: 59,
sellDate: '2023-08-28',
sellTime: '08:01',
inStock: 0,
},
{
brand: 'Eidel',
model: 'HL Road Tire',
price: 279.99,
sellDate: '2023-10-02',
sellTime: '13:23',
inStock: 14,
},
{
brand: 'Jetpulse',
model: 'Racing Socks',
price: 30,
sellDate: '2023-10-11',
sellTime: '01:23',
inStock: 16,
},
{
brand: 'Gigabox',
model: 'HL Mountain Frame',
price: 1890.9,
sellDate: '2023-05-03',
sellTime: '11:27',
inStock: 18,
},
{
brand: 'Camido',
model: 'Cycling Cap',
price: 130.1,
sellDate: '2023-03-27',
sellTime: '03:17',
inStock: 3,
},
{
brand: 'Chatterpoint',
model: 'Road Tire Tube',
price: 59,
sellDate: '2023-08-28',
sellTime: '08:01',
inStock: 0,
},
{
brand: 'Vinte',
model: 'ML Road Frame-W',
price: 30,
sellDate: '2023-10-11',
sellTime: '01:23',
inStock: 2,
},
{},
],
columns: [
{
type: 'text',
data: 'brand',
},
{
type: 'text',
data: 'model',
},
{
type: 'numeric',
data: 'price',
locale: 'en-US',
numericFormat: {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2,
},
},
{
type: 'intl-date',
data: 'sellDate',
locale: 'en-US',
dateFormat: { month: 'short', day: 'numeric', year: 'numeric' },
className: 'htRight',
},
{
type: 'intl-time',
data: 'sellTime',
locale: 'en-US',
timeFormat: { hour: '2-digit', minute: '2-digit', hour12: true },
className: 'htRight',
},
{
type: 'numeric',
data: 'inStock',
className: 'htCenter',
},
],
height: 200,
stretchH: 'all',
fixedRowsTop: 1,
fixedRowsBottom: 1,
colHeaders: true,
columnSorting: true,
// `afterColumnSort()` is a Handsontable hook: it's fired after each sorting
afterColumnSort() {
const lastRowIndex = hot.countRows() - 1;
// after each sorting, take row 1 and change its index to 0
hot.rowIndexMapper.moveIndexes(hot.toVisualRow(0), 0);
// after each sorting, take row 16 and change its index to 15
hot.rowIndexMapper.moveIndexes(hot.toVisualRow(lastRowIndex), lastRowIndex);
},
cells(row) {
const lastRowIndex = this.instance.countRows() - 1;
if (row === 0) {
return {
type: 'text',
className: 'htCenter',
readOnly: true,
};
}
if (row === lastRowIndex) {
return {
type: 'numeric',
className: 'htCenter',
};
}
return {
type: 'text',
};
},
columnSummary: [
{
sourceColumn: 2,
type: 'sum',
reversedRowCoords: true,
destinationRow: 0,
destinationColumn: 2,
forceNumeric: true,
suppressDataTypeErrors: true,
},
{
sourceColumn: 5,
type: 'sum',
reversedRowCoords: true,
destinationRow: 0,
destinationColumn: 5,
forceNumeric: true,
suppressDataTypeErrors: true,
},
],
autoWrapRow: true,
autoWrapCol: true,
licenseKey: 'non-commercial-and-evaluation',
});
TypeScript
import Handsontable from 'handsontable/base';
import { registerAllModules } from 'handsontable/registry';
// Register all Handsontable's modules.
registerAllModules();
const container = document.querySelector('#exampleExcludeRowsFromSorting')!;
const hot = new Handsontable(container, {
data: [
{
brand: 'Brand',
model: 'Model',
price: 'Price',
sellDate: 'Date',
sellTime: 'Time',
inStock: 'In stock',
},
{
brand: 'Gigabox',
model: 'HL Mountain Frame',
price: 1890.9,
sellDate: '2023-05-03',
sellTime: '11:27',
inStock: 11,
},
{
brand: 'Camido',
model: 'Cycling Cap',
price: 130.1,
sellDate: '2023-03-27',
sellTime: '03:17',
inStock: 0,
},
{
brand: 'Chatterpoint',
model: 'Road Tire Tube',
price: 59,
sellDate: '2023-08-28',
sellTime: '08:01',
inStock: 1,
},
{
brand: 'Eidel',
model: 'HL Road Tire',
price: 279.99,
sellDate: '2023-10-02',
sellTime: '13:23',
inStock: 3,
},
{
brand: 'Jetpulse',
model: 'Racing Socks',
price: 30,
sellDate: '2023-10-11',
sellTime: '01:23',
inStock: 5,
},
{
brand: 'Gigabox',
model: 'HL Mountain Frame',
price: 1890.9,
sellDate: '2023-05-03',
sellTime: '11:27',
inStock: 22,
},
{
brand: 'Camido',
model: 'Cycling Cap',
price: 130.1,
sellDate: '2023-03-27',
sellTime: '03:17',
inStock: 13,
},
{
brand: 'Chatterpoint',
model: 'Road Tire Tube',
price: 59,
sellDate: '2023-08-28',
sellTime: '08:01',
inStock: 0,
},
{
brand: 'Eidel',
model: 'HL Road Tire',
price: 279.99,
sellDate: '2023-10-02',
sellTime: '13:23',
inStock: 14,
},
{
brand: 'Jetpulse',
model: 'Racing Socks',
price: 30,
sellDate: '2023-10-11',
sellTime: '01:23',
inStock: 16,
},
{
brand: 'Gigabox',
model: 'HL Mountain Frame',
price: 1890.9,
sellDate: '2023-05-03',
sellTime: '11:27',
inStock: 18,
},
{
brand: 'Camido',
model: 'Cycling Cap',
price: 130.1,
sellDate: '2023-03-27',
sellTime: '03:17',
inStock: 3,
},
{
brand: 'Chatterpoint',
model: 'Road Tire Tube',
price: 59,
sellDate: '2023-08-28',
sellTime: '08:01',
inStock: 0,
},
{
brand: 'Vinte',
model: 'ML Road Frame-W',
price: 30,
sellDate: '2023-10-11',
sellTime: '01:23',
inStock: 2,
},
{},
],
columns: [
{
type: 'text',
data: 'brand',
},
{
type: 'text',
data: 'model',
},
{
type: 'numeric',
data: 'price',
locale: 'en-US',
numericFormat: {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2,
},
},
{
type: 'intl-date',
data: 'sellDate',
locale: 'en-US',
dateFormat: { month: 'short', day: 'numeric', year: 'numeric' },
className: 'htRight',
},
{
type: 'intl-time',
data: 'sellTime',
locale: 'en-US',
timeFormat: { hour: '2-digit', minute: '2-digit', hour12: true },
className: 'htRight',
},
{
type: 'numeric',
data: 'inStock',
className: 'htCenter',
},
],
height: 200,
stretchH: 'all',
fixedRowsTop: 1,
fixedRowsBottom: 1,
colHeaders: true,
columnSorting: true,
// `afterColumnSort()` is a Handsontable hook: it's fired after each sorting
afterColumnSort() {
const lastRowIndex = hot.countRows() - 1;
// after each sorting, take row 1 and change its index to 0
hot.rowIndexMapper.moveIndexes(hot.toVisualRow(0), 0);
// after each sorting, take row 16 and change its index to 15
hot.rowIndexMapper.moveIndexes(hot.toVisualRow(lastRowIndex), lastRowIndex);
},
cells(row) {
const lastRowIndex = this.instance.countRows() - 1;
if (row === 0) {
return {
type: 'text',
className: 'htCenter',
readOnly: true,
};
}
if (row === lastRowIndex) {
return {
type: 'numeric',
className: 'htCenter',
};
}
return {
type: 'text',
};
},
columnSummary: [
{
sourceColumn: 2,
type: 'sum',
reversedRowCoords: true,
destinationRow: 0,
destinationColumn: 2,
forceNumeric: true,
suppressDataTypeErrors: true,
},
{
sourceColumn: 5,
type: 'sum',
reversedRowCoords: true,
destinationRow: 0,
destinationColumn: 5,
forceNumeric: true,
suppressDataTypeErrors: true,
},
],
autoWrapRow: true,
autoWrapCol: true,
licenseKey: 'non-commercial-and-evaluation',
});
HTML
<div id="exampleExcludeRowsFromSorting"></div>

Control sorting programmatically

Use the ColumnSorting plugin API and updateSettings() to control sorting at runtime. This lets you, for example, enable or disable sorting based on conditions, or trigger sorting from outside the grid.

Enable or disable sorting programmatically

To enable or disable sorting programmatically, call updateSettings() with columnSorting set to true or false.

// enable sorting for all columns
handsontableInstance.updateSettings({
columnSorting: true,
});
// disable sorting for all columns
handsontableInstance.updateSettings({
columnSorting: false,
});
// enable sorting on column 0, disable sorting on column 1
handsontableInstance.updateSettings({
columns: [
{ columnSorting: { headerAction: true } },
{ columnSorting: { headerAction: false } },
],
});

Sort data programmatically

Use columnSorting.sort() to sort programmatically. Pass an object with column (visual column index) and sortOrder ('asc' or 'desc'). Each call replaces the previous sort order entirely.

Use columnSorting.clearSort() to remove the active sort and return rows to their original order.

const columnSorting = handsontableInstance.getPlugin('columnSorting');
// sort column 0 in ascending order
columnSorting.sort({ column: 0, sortOrder: 'asc' });
// return rows to their original order
columnSorting.clearSort();

To see how it works, try out the following demo:

JavaScript
import Handsontable from 'handsontable/base';
import { registerAllModules } from 'handsontable/registry';
// Register all Handsontable's modules.
registerAllModules();
const container = document.querySelector('#exampleSortByAPI');
const hot = new Handsontable(container, {
data: [
{
brand: 'Jetpulse',
model: 'Racing Socks',
price: 30,
sellDate: '2023-10-11',
sellTime: '01:23',
inStock: false,
},
{
brand: 'Gigabox',
model: 'HL Mountain Frame',
price: 1890.9,
sellDate: '2023-05-03',
sellTime: '11:27',
inStock: false,
},
{
brand: 'Camido',
model: 'Cycling Cap',
price: 130.1,
sellDate: '2023-03-27',
sellTime: '03:17',
inStock: true,
},
{
brand: 'Chatterpoint',
model: 'Road Tire Tube',
price: 59,
sellDate: '2023-08-28',
sellTime: '08:01',
inStock: true,
},
{
brand: 'Eidel',
model: 'HL Road Tire',
price: 279.99,
sellDate: '2023-10-02',
sellTime: '13:23',
inStock: true,
},
],
columns: [
{
title: 'Brand',
type: 'text',
data: 'brand',
},
{
title: 'Model',
type: 'text',
data: 'model',
},
{
title: 'Price',
type: 'numeric',
data: 'price',
locale: 'en-US',
numericFormat: {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2,
},
},
{
title: 'Date',
type: 'intl-date',
data: 'sellDate',
locale: 'en-US',
dateFormat: { month: 'short', day: 'numeric', year: 'numeric' },
className: 'htRight',
},
{
title: 'Time',
type: 'intl-time',
data: 'sellTime',
locale: 'en-US',
timeFormat: { hour: '2-digit', minute: '2-digit', hour12: true },
className: 'htRight',
},
{
title: 'In stock',
type: 'checkbox',
data: 'inStock',
className: 'htCenter',
},
],
columnSorting: true,
height: 'auto',
stretchH: 'all',
autoWrapRow: true,
autoWrapCol: true,
licenseKey: 'non-commercial-and-evaluation',
});
const columnSorting = hot.getPlugin('columnSorting');
const buttonSortAscending = document.querySelector('#sort_asc');
buttonSortAscending.addEventListener('click', () => {
columnSorting.sort({
column: 0,
sortOrder: 'asc',
});
});
const buttonUnsort = document.querySelector('#unsort');
buttonUnsort.addEventListener('click', () => {
columnSorting.clearSort();
});
TypeScript
import Handsontable from 'handsontable/base';
import { registerAllModules } from 'handsontable/registry';
import { ColumnSorting } from 'handsontable/plugins';
// Register all Handsontable's modules.
registerAllModules();
const container = document.querySelector('#exampleSortByAPI')!;
const hot = new Handsontable(container, {
data: [
{
brand: 'Jetpulse',
model: 'Racing Socks',
price: 30,
sellDate: '2023-10-11',
sellTime: '01:23',
inStock: false,
},
{
brand: 'Gigabox',
model: 'HL Mountain Frame',
price: 1890.9,
sellDate: '2023-05-03',
sellTime: '11:27',
inStock: false,
},
{
brand: 'Camido',
model: 'Cycling Cap',
price: 130.1,
sellDate: '2023-03-27',
sellTime: '03:17',
inStock: true,
},
{
brand: 'Chatterpoint',
model: 'Road Tire Tube',
price: 59,
sellDate: '2023-08-28',
sellTime: '08:01',
inStock: true,
},
{
brand: 'Eidel',
model: 'HL Road Tire',
price: 279.99,
sellDate: '2023-10-02',
sellTime: '13:23',
inStock: true,
},
],
columns: [
{
title: 'Brand',
type: 'text',
data: 'brand',
},
{
title: 'Model',
type: 'text',
data: 'model',
},
{
title: 'Price',
type: 'numeric',
data: 'price',
locale: 'en-US',
numericFormat: {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2,
},
},
{
title: 'Date',
type: 'intl-date',
data: 'sellDate',
locale: 'en-US',
dateFormat: { month: 'short', day: 'numeric', year: 'numeric' },
className: 'htRight',
},
{
title: 'Time',
type: 'intl-time',
data: 'sellTime',
locale: 'en-US',
timeFormat: { hour: '2-digit', minute: '2-digit', hour12: true },
className: 'htRight',
},
{
title: 'In stock',
type: 'checkbox',
data: 'inStock',
className: 'htCenter',
},
],
columnSorting: true,
height: 'auto',
stretchH: 'all',
autoWrapRow: true,
autoWrapCol: true,
licenseKey: 'non-commercial-and-evaluation',
});
const columnSorting: ColumnSorting = hot.getPlugin('columnSorting');
const buttonSortAscending = document.querySelector('#sort_asc')!;
buttonSortAscending.addEventListener('click', () => {
columnSorting.sort({
column: 0,
sortOrder: 'asc',
});
});
const buttonUnsort = document.querySelector('#unsort')!;
buttonUnsort.addEventListener('click', () => {
columnSorting.clearSort();
});
HTML
<div class="example-controls-container">
<div class="controls">
<button id="sort_asc">Sort by the "Brand" column, in ascending order</button>
<button id="unsort">Go back to the original order</button>
</div>
</div>
<div id="exampleSortByAPI"></div>

MultiColumnSorting plugin

The MultiColumnSorting plugin extends ColumnSorting to sort rows by multiple columns at the same time.

Key differences from ColumnSorting:

  • Sort by multiple columns at once. The column clicked first has the highest sort priority.
  • Hold Ctrl/Cmd and click a column header to add it to the active sort criteria without replacing the existing sort.
  • Press Shift+Enter with a column header focused to append that column to the active sort criteria.
  • initialConfig accepts an array of sort config objects to define a multi-column initial order.

ColumnSorting and MultiColumnSorting are mutually exclusive. If both are set to true, ColumnSorting is automatically disabled.

Enable multi-column sorting

To enable multi-column sorting for all columns, set multiColumnSorting to true.

const configurationOptions = {
multiColumnSorting: true,
};

Configure multi-column sorting options

multiColumnSorting supports the same options as columnSorting: headerAction, sortEmptyCells, indicator, and compareFunctionFactory. Refer to Configure sorting for a description of each option.

To disable multi-column sorting for a specific column, set headerAction to false in that column’s configuration:

const configurationOptions = {
multiColumnSorting: true,
columns: [
{
multiColumnSorting: {
headerAction: false, // disable multi-column sorting for the first column
},
},
],
};

Sort by multiple columns

To sort by multiple columns interactively, hold Ctrl/Cmd and click column headers in the desired priority order.

Try the following demo:

  1. Click Brand. The rows sort by brand.
  2. Hold Ctrl/Cmd and click Model. The rows sort by model within each brand.
  3. Hold Ctrl/Cmd and click Price. The rows sort by price within each model.
JavaScript
import Handsontable from 'handsontable/base';
import { registerAllModules } from 'handsontable/registry';
// Register all Handsontable's modules.
registerAllModules();
const container = document.querySelector('#exampleSortByMultipleColumns');
new Handsontable(container, {
data: [
{
brand: 'Jetpulse',
model: 'HL Mountain Frame',
price: 1890.9,
sellDate: '2023-10-11',
sellTime: '01:23',
inStock: false,
},
{
brand: 'Jetpulse',
model: 'HL Mountain Frame',
price: 30,
sellDate: '2023-05-03',
sellTime: '11:27',
inStock: false,
},
{
brand: 'Jetpulse',
model: 'Cycling Cap',
price: 130.1,
sellDate: '2023-03-27',
sellTime: '03:17',
inStock: true,
},
{
brand: 'Chatterpoint',
model: 'Road Tire Tube',
price: 279.99,
sellDate: '2023-08-28',
sellTime: '08:01',
inStock: true,
},
{
brand: 'Chatterpoint',
model: 'HL Road Tire',
price: 59,
sellDate: '2023-10-02',
sellTime: '13:23',
inStock: true,
},
],
columns: [
{
title: 'Brand',
type: 'text',
data: 'brand',
},
{
title: 'Model',
type: 'text',
data: 'model',
},
{
title: 'Price',
type: 'numeric',
data: 'price',
locale: 'en-US',
numericFormat: {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2,
},
},
{
title: 'Date',
type: 'intl-date',
data: 'sellDate',
locale: 'en-US',
dateFormat: { month: 'short', day: 'numeric', year: 'numeric' },
className: 'htRight',
},
{
title: 'Time',
type: 'intl-time',
data: 'sellTime',
locale: 'en-US',
timeFormat: { hour: '2-digit', minute: '2-digit', hour12: true },
className: 'htRight',
},
{
title: 'In stock',
type: 'checkbox',
data: 'inStock',
className: 'htCenter',
},
],
// enable sorting by multiple columns, for all columns
multiColumnSorting: true,
height: 'auto',
stretchH: 'all',
autoWrapRow: true,
autoWrapCol: true,
licenseKey: 'non-commercial-and-evaluation',
});
TypeScript
import Handsontable from 'handsontable/base';
import { registerAllModules } from 'handsontable/registry';
// Register all Handsontable's modules.
registerAllModules();
const container = document.querySelector('#exampleSortByMultipleColumns')!;
new Handsontable(container, {
data: [
{
brand: 'Jetpulse',
model: 'HL Mountain Frame',
price: 1890.9,
sellDate: '2023-10-11',
sellTime: '01:23',
inStock: false,
},
{
brand: 'Jetpulse',
model: 'HL Mountain Frame',
price: 30,
sellDate: '2023-05-03',
sellTime: '11:27',
inStock: false,
},
{
brand: 'Jetpulse',
model: 'Cycling Cap',
price: 130.1,
sellDate: '2023-03-27',
sellTime: '03:17',
inStock: true,
},
{
brand: 'Chatterpoint',
model: 'Road Tire Tube',
price: 279.99,
sellDate: '2023-08-28',
sellTime: '08:01',
inStock: true,
},
{
brand: 'Chatterpoint',
model: 'HL Road Tire',
price: 59,
sellDate: '2023-10-02',
sellTime: '13:23',
inStock: true,
},
],
columns: [
{
title: 'Brand',
type: 'text',
data: 'brand',
},
{
title: 'Model',
type: 'text',
data: 'model',
},
{
title: 'Price',
type: 'numeric',
data: 'price',
locale: 'en-US',
numericFormat: {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2,
},
},
{
title: 'Date',
type: 'intl-date',
data: 'sellDate',
locale: 'en-US',
dateFormat: { month: 'short', day: 'numeric', year: 'numeric' },
className: 'htRight',
},
{
title: 'Time',
type: 'intl-time',
data: 'sellTime',
locale: 'en-US',
timeFormat: { hour: '2-digit', minute: '2-digit', hour12: true },
className: 'htRight',
},
{
title: 'In stock',
type: 'checkbox',
data: 'inStock',
className: 'htCenter',
},
],
// enable sorting by multiple columns, for all columns
multiColumnSorting: true,
height: 'auto',
stretchH: 'all',
autoWrapRow: true,
autoWrapCol: true,
licenseKey: 'non-commercial-and-evaluation',
});
HTML
<div id="exampleSortByMultipleColumns"></div>

Set an initial multi-column sort order

Use initialConfig with an array of sort config objects to apply a multi-column sort at initialization. Each object has a column property (visual column index) and a sortOrder property ('asc' or 'desc'). The array order determines sort priority: the first entry has the highest priority.

In the following demo, the data is initially sorted by Brand ascending, then by Model descending:

JavaScript
import Handsontable from 'handsontable/base';
import { registerAllModules } from 'handsontable/registry';
// Register all Handsontable's modules.
registerAllModules();
const container = document.querySelector('#exampleInitialSortOrder');
new Handsontable(container, {
data: [
{
brand: 'Jetpulse',
model: 'HL Mountain Frame',
price: 30,
sellDate: '2023-10-11',
sellTime: '01:23',
inStock: false,
},
{
brand: 'Jetpulse',
model: 'HL Mountain Frame',
price: 1890.9,
sellDate: '2023-05-03',
sellTime: '11:27',
inStock: false,
},
{
brand: 'Jetpulse',
model: 'Cycling Cap',
price: 130.1,
sellDate: '2023-03-27',
sellTime: '03:17',
inStock: true,
},
{
brand: 'Chatterpoint',
model: 'Road Tire Tube',
price: 59,
sellDate: '2023-08-28',
sellTime: '08:01',
inStock: true,
},
{
brand: 'Chatterpoint',
model: 'HL Road Tire',
price: 279.99,
sellDate: '2023-10-02',
sellTime: '13:23',
inStock: true,
},
],
columns: [
{
title: 'Brand',
type: 'text',
data: 'brand',
},
{
title: 'Model',
type: 'text',
data: 'model',
},
{
title: 'Price',
type: 'numeric',
data: 'price',
locale: 'en-US',
numericFormat: {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2,
},
},
{
title: 'Date',
type: 'intl-date',
data: 'sellDate',
locale: 'en-US',
dateFormat: { month: 'short', day: 'numeric', year: 'numeric' },
className: 'htRight',
},
{
title: 'Time',
type: 'intl-time',
data: 'sellTime',
locale: 'en-US',
timeFormat: { hour: '2-digit', minute: '2-digit', hour12: true },
className: 'htRight',
},
{
title: 'In stock',
type: 'checkbox',
data: 'inStock',
className: 'htCenter',
},
],
multiColumnSorting: {
initialConfig: [
// at initialization, sort the data by the 'Brand' column, in ascending order
{
column: 0,
sortOrder: 'asc',
},
// at initialization, sort the data by the 'Model' column, in descending order
{
column: 1,
sortOrder: 'desc',
},
],
},
height: 'auto',
stretchH: 'all',
autoWrapRow: true,
autoWrapCol: true,
licenseKey: 'non-commercial-and-evaluation',
});
TypeScript
import Handsontable from 'handsontable/base';
import { registerAllModules } from 'handsontable/registry';
// Register all Handsontable's modules.
registerAllModules();
const container = document.querySelector('#exampleInitialSortOrder')!;
new Handsontable(container, {
data: [
{
brand: 'Jetpulse',
model: 'HL Mountain Frame',
price: 30,
sellDate: '2023-10-11',
sellTime: '01:23',
inStock: false,
},
{
brand: 'Jetpulse',
model: 'HL Mountain Frame',
price: 1890.9,
sellDate: '2023-05-03',
sellTime: '11:27',
inStock: false,
},
{
brand: 'Jetpulse',
model: 'Cycling Cap',
price: 130.1,
sellDate: '2023-03-27',
sellTime: '03:17',
inStock: true,
},
{
brand: 'Chatterpoint',
model: 'Road Tire Tube',
price: 59,
sellDate: '2023-08-28',
sellTime: '08:01',
inStock: true,
},
{
brand: 'Chatterpoint',
model: 'HL Road Tire',
price: 279.99,
sellDate: '2023-10-02',
sellTime: '13:23',
inStock: true,
},
],
columns: [
{
title: 'Brand',
type: 'text',
data: 'brand',
},
{
title: 'Model',
type: 'text',
data: 'model',
},
{
title: 'Price',
type: 'numeric',
data: 'price',
locale: 'en-US',
numericFormat: {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2,
},
},
{
title: 'Date',
type: 'intl-date',
data: 'sellDate',
locale: 'en-US',
dateFormat: { month: 'short', day: 'numeric', year: 'numeric' },
className: 'htRight',
},
{
title: 'Time',
type: 'intl-time',
data: 'sellTime',
locale: 'en-US',
timeFormat: { hour: '2-digit', minute: '2-digit', hour12: true },
className: 'htRight',
},
{
title: 'In stock',
type: 'checkbox',
data: 'inStock',
className: 'htCenter',
},
],
multiColumnSorting: {
initialConfig: [
// at initialization, sort the data by the 'Brand' column, in ascending order
{
column: 0,
sortOrder: 'asc',
},
// at initialization, sort the data by the 'Model' column, in descending order
{
column: 1,
sortOrder: 'desc',
},
],
},
height: 'auto',
stretchH: 'all',
autoWrapRow: true,
autoWrapCol: true,
licenseKey: 'non-commercial-and-evaluation',
});
HTML
<div id="exampleInitialSortOrder"></div>
const configurationOptions = {
multiColumnSorting: {
initialConfig: [
{ column: 0, sortOrder: 'asc' }, // sort column 0 ascending (highest priority)
{ column: 1, sortOrder: 'desc' }, // sort column 1 descending (next priority)
],
},
};

Sort by multiple columns programmatically

Use multiColumnSorting.sort() to sort by multiple columns programmatically. Pass an array of sort config objects. The array order determines sort priority. Each call replaces the previous sort order entirely.

Use multiColumnSorting.clearSort() to remove all sort criteria and return rows to their original order.

const multiColumnSorting = handsontableInstance.getPlugin('multiColumnSorting');
// sort column 0 ascending, then column 1 descending
multiColumnSorting.sort([
{ column: 0, sortOrder: 'asc' },
{ column: 1, sortOrder: 'desc' },
]);
// return rows to their original order
multiColumnSorting.clearSort();

To see how it works, try out the following demo:

JavaScript
import Handsontable from 'handsontable/base';
import { registerAllModules } from 'handsontable/registry';
// Register all Handsontable's modules.
registerAllModules();
const container = document.querySelector('#exampleSortByAPIMultipleColumns');
const hot = new Handsontable(container, {
data: [
{
brand: 'Jetpulse',
model: 'Racing Socks',
price: 30,
sellDate: '2023-10-11',
sellTime: '01:23',
inStock: false,
},
{
brand: 'Jetpulse',
model: 'HL Mountain Frame',
price: 1890.9,
sellDate: '2023-05-03',
sellTime: '11:27',
inStock: false,
},
{
brand: 'Jetpulse',
model: 'Cycling Cap',
price: 130.1,
sellDate: '2023-03-27',
sellTime: '03:17',
inStock: true,
},
{
brand: 'Chatterpoint',
model: 'Road Tire Tube',
price: 59,
sellDate: '2023-08-28',
sellTime: '08:01',
inStock: true,
},
{
brand: 'Chatterpoint',
model: 'HL Road Tire',
price: 279.99,
sellDate: '2023-10-02',
sellTime: '13:23',
inStock: true,
},
],
columns: [
{
title: 'Brand',
type: 'text',
data: 'brand',
},
{
title: 'Model',
type: 'text',
data: 'model',
},
{
title: 'Price',
type: 'numeric',
data: 'price',
locale: 'en-US',
numericFormat: {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2,
},
},
{
title: 'Date',
type: 'intl-date',
data: 'sellDate',
locale: 'en-US',
dateFormat: { month: 'short', day: 'numeric', year: 'numeric' },
className: 'htRight',
},
{
title: 'Time',
type: 'intl-time',
data: 'sellTime',
locale: 'en-US',
timeFormat: { hour: '2-digit', minute: '2-digit', hour12: true },
className: 'htRight',
},
{
title: 'In stock',
type: 'checkbox',
data: 'inStock',
className: 'htCenter',
},
],
multiColumnSorting: true,
height: 'auto',
stretchH: 'all',
autoWrapRow: true,
autoWrapCol: true,
licenseKey: 'non-commercial-and-evaluation',
});
const multiColumnSorting = hot.getPlugin('multiColumnSorting');
const buttonSort = document.querySelector('#sort');
buttonSort.addEventListener('click', () => {
multiColumnSorting.sort([
{
column: 0,
sortOrder: 'asc',
},
{
column: 1,
sortOrder: 'desc',
},
]);
});
TypeScript
import Handsontable from 'handsontable/base';
import { registerAllModules } from 'handsontable/registry';
import { MultiColumnSorting } from 'handsontable/plugins';
// Register all Handsontable's modules.
registerAllModules();
const container = document.querySelector('#exampleSortByAPIMultipleColumns')!;
const hot = new Handsontable(container, {
data: [
{
brand: 'Jetpulse',
model: 'Racing Socks',
price: 30,
sellDate: '2023-10-11',
sellTime: '01:23',
inStock: false,
},
{
brand: 'Jetpulse',
model: 'HL Mountain Frame',
price: 1890.9,
sellDate: '2023-05-03',
sellTime: '11:27',
inStock: false,
},
{
brand: 'Jetpulse',
model: 'Cycling Cap',
price: 130.1,
sellDate: '2023-03-27',
sellTime: '03:17',
inStock: true,
},
{
brand: 'Chatterpoint',
model: 'Road Tire Tube',
price: 59,
sellDate: '2023-08-28',
sellTime: '08:01',
inStock: true,
},
{
brand: 'Chatterpoint',
model: 'HL Road Tire',
price: 279.99,
sellDate: '2023-10-02',
sellTime: '13:23',
inStock: true,
},
],
columns: [
{
title: 'Brand',
type: 'text',
data: 'brand',
},
{
title: 'Model',
type: 'text',
data: 'model',
},
{
title: 'Price',
type: 'numeric',
data: 'price',
locale: 'en-US',
numericFormat: {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2,
},
},
{
title: 'Date',
type: 'intl-date',
data: 'sellDate',
locale: 'en-US',
dateFormat: { month: 'short', day: 'numeric', year: 'numeric' },
className: 'htRight',
},
{
title: 'Time',
type: 'intl-time',
data: 'sellTime',
locale: 'en-US',
timeFormat: { hour: '2-digit', minute: '2-digit', hour12: true },
className: 'htRight',
},
{
title: 'In stock',
type: 'checkbox',
data: 'inStock',
className: 'htCenter',
},
],
multiColumnSorting: true,
height: 'auto',
stretchH: 'all',
autoWrapRow: true,
autoWrapCol: true,
licenseKey: 'non-commercial-and-evaluation',
});
const multiColumnSorting: MultiColumnSorting = hot.getPlugin('multiColumnSorting');
const buttonSort = document.querySelector('#sort')!;
buttonSort.addEventListener('click', () => {
multiColumnSorting.sort([
{
column: 0,
sortOrder: 'asc',
},
{
column: 1,
sortOrder: 'desc',
},
]);
});
HTML
<div class="example-controls-container">
<div class="controls">
<button id="sort">Sort</button>
</div>
</div>
<div id="exampleSortByAPIMultipleColumns"></div>

Add custom sort icons

The default sort icons (↑↓) are rendered using CSS -webkit-mask-image. Override the following pseudo-elements to replace them:

  • .columnSorting.sortAction.ascending::before
  • .columnSorting.sortAction.descending::before
JavaScript
import Handsontable from 'handsontable/base';
import { registerAllModules } from 'handsontable/registry';
// Register all Handsontable's modules.
registerAllModules();
const container = document.querySelector('#exampleCustomSortIcons');
new Handsontable(container, {
data: [
{
brand: 'Jetpulse',
model: 'Racing Socks',
price: 30,
sellDate: '2023-10-11',
sellTime: '01:23',
inStock: false,
},
{
brand: 'Gigabox',
model: 'HL Mountain Frame',
price: 1890.9,
sellDate: '2023-05-03',
sellTime: '11:27',
inStock: false,
},
{
brand: 'Camido',
model: 'Cycling Cap',
price: 130.1,
sellDate: '2023-03-27',
sellTime: '03:17',
inStock: true,
},
{
brand: 'Chatterpoint',
model: 'Road Tire Tube',
price: 59,
sellDate: '2023-08-28',
sellTime: '08:01',
inStock: true,
},
{
brand: 'Eidel',
model: 'HL Road Tire',
price: 279.99,
sellDate: '2023-10-02',
sellTime: '13:23',
inStock: true,
},
],
columns: [
{
title: 'Brand',
type: 'text',
data: 'brand',
},
{
title: 'Model',
type: 'text',
data: 'model',
},
{
title: 'Price',
type: 'numeric',
data: 'price',
locale: 'en-US',
numericFormat: {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2,
},
},
{
title: 'Date',
type: 'intl-date',
data: 'sellDate',
locale: 'en-US',
dateFormat: { month: 'short', day: 'numeric', year: 'numeric' },
className: 'htRight',
},
{
title: 'Time',
type: 'intl-time',
data: 'sellTime',
locale: 'en-US',
timeFormat: { hour: '2-digit', minute: '2-digit', hour12: true },
className: 'htRight',
},
{
title: 'In stock',
type: 'checkbox',
data: 'inStock',
className: 'htCenter',
},
],
className: 'custom-sort-icon-example-1',
columnSorting: {
initialConfig: {
column: 1,
sortOrder: 'desc',
},
},
height: 'auto',
stretchH: 'all',
autoWrapRow: true,
autoWrapCol: true,
licenseKey: 'non-commercial-and-evaluation',
});
TypeScript
import Handsontable from 'handsontable/base';
import { registerAllModules } from 'handsontable/registry';
// Register all Handsontable's modules.
registerAllModules();
const container = document.querySelector('#exampleCustomSortIcons')!;
new Handsontable(container, {
data: [
{
brand: 'Jetpulse',
model: 'Racing Socks',
price: 30,
sellDate: '2023-10-11',
sellTime: '01:23',
inStock: false,
},
{
brand: 'Gigabox',
model: 'HL Mountain Frame',
price: 1890.9,
sellDate: '2023-05-03',
sellTime: '11:27',
inStock: false,
},
{
brand: 'Camido',
model: 'Cycling Cap',
price: 130.1,
sellDate: '2023-03-27',
sellTime: '03:17',
inStock: true,
},
{
brand: 'Chatterpoint',
model: 'Road Tire Tube',
price: 59,
sellDate: '2023-08-28',
sellTime: '08:01',
inStock: true,
},
{
brand: 'Eidel',
model: 'HL Road Tire',
price: 279.99,
sellDate: '2023-10-02',
sellTime: '13:23',
inStock: true,
},
],
columns: [
{
title: 'Brand',
type: 'text',
data: 'brand',
},
{
title: 'Model',
type: 'text',
data: 'model',
},
{
title: 'Price',
type: 'numeric',
data: 'price',
locale: 'en-US',
numericFormat: {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2,
},
},
{
title: 'Date',
type: 'intl-date',
data: 'sellDate',
locale: 'en-US',
dateFormat: { month: 'short', day: 'numeric', year: 'numeric' },
className: 'htRight',
},
{
title: 'Time',
type: 'intl-time',
data: 'sellTime',
locale: 'en-US',
timeFormat: { hour: '2-digit', minute: '2-digit', hour12: true },
className: 'htRight',
},
{
title: 'In stock',
type: 'checkbox',
data: 'inStock',
className: 'htCenter',
},
],
className: 'custom-sort-icon-example-1',
columnSorting: {
initialConfig: {
column: 1,
sortOrder: 'desc',
},
},
height: 'auto',
stretchH: 'all',
autoWrapRow: true,
autoWrapCol: true,
licenseKey: 'non-commercial-and-evaluation',
});
HTML
<div id="exampleCustomSortIcons"></div>
CSS
.custom-sort-icon-example-1 .columnSorting.sortAction.ascending::before {
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M0 0h24v24H0z' stroke='none'/%3E%3Cpath d='M12 21V9M8 13l4-4 4 4'/%3E%3Cpath d='M21 12a9 9 0 0 0-18 0'/%3E%3C/svg%3E");
}
.custom-sort-icon-example-1 .columnSorting.sortAction.descending::before {
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M0 0h24v24H0z' stroke='none'/%3E%3Cpath d='M12 3v12M16 11l-4 4-4-4'/%3E%3Cpath d='M3 12a9 9 0 0 0 18 0'/%3E%3C/svg%3E");
}

To replace the column-priority number indicators used by the MultiColumnSorting plugin (1, 2, etc.), override the content of .columnSorting.sort-1::after and subsequent pseudo-elements:

JavaScript
import Handsontable from 'handsontable/base';
import { registerAllModules } from 'handsontable/registry';
// Register all Handsontable's modules.
registerAllModules();
const container = document.querySelector('#exampleCustomSortIcons3');
new Handsontable(container, {
data: [
{
brand: 'Jetpulse',
model: 'Racing Socks',
color: 'White',
price: 30,
sellDate: '2023-10-11',
sellTime: '01:23',
inStock: false,
},
{
brand: 'Gigabox',
model: 'HL Frame',
color: 'Black',
price: 1890.9,
sellDate: '2023-05-03',
sellTime: '11:27',
inStock: false,
},
{
brand: 'Camido',
model: 'Cycling Cap',
color: 'Red',
price: 130.1,
sellDate: '2023-03-27',
sellTime: '03:17',
inStock: true,
},
{
brand: 'Chatterpoint',
model: 'Road Tire Tube',
color: 'Green',
price: 59,
sellDate: '2023-08-28',
sellTime: '08:01',
inStock: true,
},
{
brand: 'Eidel',
model: 'HL Road Tire',
color: 'Blue',
price: 279.99,
sellDate: '2023-10-02',
sellTime: '13:23',
inStock: true,
},
],
columns: [
{
title: 'Brand',
type: 'text',
data: 'brand',
},
{
title: 'Model',
type: 'text',
data: 'model',
},
{
title: 'Color',
type: 'text',
data: 'color',
},
{
title: 'Price',
type: 'numeric',
data: 'price',
locale: 'en-US',
numericFormat: {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2,
},
},
{
title: 'Date',
type: 'intl-date',
data: 'sellDate',
locale: 'en-US',
dateFormat: { month: 'short', day: 'numeric', year: 'numeric' },
className: 'htRight',
},
{
title: 'Time',
type: 'intl-time',
data: 'sellTime',
locale: 'en-US',
timeFormat: { hour: '2-digit', minute: '2-digit', hour12: true },
className: 'htRight',
},
{
title: 'In stock',
type: 'checkbox',
data: 'inStock',
className: 'htCenter',
},
],
className: 'custom-sort-icon-example-3',
multiColumnSorting: {
initialConfig: [
{
column: 0,
sortOrder: 'asc',
},
{
column: 1,
sortOrder: 'desc',
},
{
column: 2,
sortOrder: 'asc',
},
{
column: 3,
sortOrder: 'desc',
},
{
column: 4,
sortOrder: 'asc',
},
{
column: 5,
sortOrder: 'desc',
},
{
column: 6,
sortOrder: 'asc',
},
],
},
height: 'auto',
stretchH: 'all',
autoWrapRow: true,
autoWrapCol: true,
licenseKey: 'non-commercial-and-evaluation',
});
TypeScript
import Handsontable from 'handsontable/base';
import { registerAllModules } from 'handsontable/registry';
// Register all Handsontable's modules.
registerAllModules();
const container = document.querySelector('#exampleCustomSortIcons3')!;
new Handsontable(container, {
data: [
{
brand: 'Jetpulse',
model: 'Racing Socks',
color: 'White',
price: 30,
sellDate: '2023-10-11',
sellTime: '01:23',
inStock: false,
},
{
brand: 'Gigabox',
model: 'HL Frame',
color: 'Black',
price: 1890.9,
sellDate: '2023-05-03',
sellTime: '11:27',
inStock: false,
},
{
brand: 'Camido',
model: 'Cycling Cap',
color: 'Red',
price: 130.1,
sellDate: '2023-03-27',
sellTime: '03:17',
inStock: true,
},
{
brand: 'Chatterpoint',
model: 'Road Tire Tube',
color: 'Green',
price: 59,
sellDate: '2023-08-28',
sellTime: '08:01',
inStock: true,
},
{
brand: 'Eidel',
model: 'HL Road Tire',
color: 'Blue',
price: 279.99,
sellDate: '2023-10-02',
sellTime: '13:23',
inStock: true,
},
],
columns: [
{
title: 'Brand',
type: 'text',
data: 'brand',
},
{
title: 'Model',
type: 'text',
data: 'model',
},
{
title: 'Color',
type: 'text',
data: 'color',
},
{
title: 'Price',
type: 'numeric',
data: 'price',
locale: 'en-US',
numericFormat: {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2,
},
},
{
title: 'Date',
type: 'intl-date',
data: 'sellDate',
locale: 'en-US',
dateFormat: { month: 'short', day: 'numeric', year: 'numeric' },
className: 'htRight',
},
{
title: 'Time',
type: 'intl-time',
data: 'sellTime',
locale: 'en-US',
timeFormat: { hour: '2-digit', minute: '2-digit', hour12: true },
className: 'htRight',
},
{
title: 'In stock',
type: 'checkbox',
data: 'inStock',
className: 'htCenter',
},
],
className: 'custom-sort-icon-example-3',
multiColumnSorting: {
initialConfig: [
{
column: 0,
sortOrder: 'asc',
},
{
column: 1,
sortOrder: 'desc',
},
{
column: 2,
sortOrder: 'asc',
},
{
column: 3,
sortOrder: 'desc',
},
{
column: 4,
sortOrder: 'asc',
},
{
column: 5,
sortOrder: 'desc',
},
{
column: 6,
sortOrder: 'asc',
},
],
},
height: 'auto',
stretchH: 'all',
autoWrapRow: true,
autoWrapCol: true,
licenseKey: 'non-commercial-and-evaluation',
});
HTML
<div id="exampleCustomSortIcons3"></div>
CSS
.custom-sort-icon-example-3 .handsontable span.colHeader.columnSorting.sort-1::after {
content: '①';
}
.custom-sort-icon-example-3 .handsontable span.colHeader.columnSorting.sort-2::after {
content: '②';
}
.custom-sort-icon-example-3 .handsontable span.colHeader.columnSorting.sort-3::after {
content: '③';
}
.custom-sort-icon-example-3 .handsontable span.colHeader.columnSorting.sort-4::after {
content: '④';
}
.custom-sort-icon-example-3 .handsontable span.colHeader.columnSorting.sort-5::after {
content: '⑤';
}
.custom-sort-icon-example-3 .handsontable span.colHeader.columnSorting.sort-6::after {
content: '⑥';
}
.custom-sort-icon-example-3 .handsontable span.colHeader.columnSorting.sort-7::after {
content: '⑦';
}
.custom-sort-icon-example-3 .handsontable span.colHeader.columnSorting::after {
width: 10px;
font-size: 10px;
}
.custom-sort-icon-example-3 .handsontable .columnSorting.sortAction:before {
right: 5px;
}

Import the sorting module

To reduce bundle size, import only the modules you need. For sorting, you need the base module and the plugin module.

// import the base module
import Handsontable from 'handsontable/base';
// import ColumnSorting (or MultiColumnSorting instead)
import { registerPlugin, ColumnSorting } from 'handsontable/plugins';
// register the plugin
registerPlugin(ColumnSorting);
WindowsmacOSActionExcelSheets
EnterEnterSort by the focused column, cycling through ascending, descending, and original order
Shift+EnterShift+EnterAppend the focused column to the active sort criteria. Requires the MultiColumnSorting plugin.

API reference

For the full list of options, methods, and hooks related to sorting, see the following API reference pages:

Troubleshooting

Didn’t find what you need? Try this: