With sorting, you can easily rearrange rows of data, based on the values in specific columns. This
is particularly useful for analyzing and organizing large data sets, which helps you identify
patterns and trends.
You can sort data in different ways:
Alphabetically, numerically, or based on a custom sorting logic
In ascending, descending, or a custom order
By a single column, or by multiple columns
Using Handsontable's UI or API
Handsontable sorts data only visually, so your source data remains in the original order. To save
your sorting changes in the data source, see this guide:
Saving data.
Sorting demo
Click on one of the column names to sort the values in ascending (↑) or descending (↓) order, or to
go back to the original order.
// to import sorting as an individual module, see the 'Import the sorting module' section of this pageimport{ HotTable }from'@handsontable/react';import{ registerAllModules }from'handsontable/registry';import'handsontable/dist/handsontable.full.min.css';// register Handsontable's modulesregisterAllModules();exportconstApp=()=>{return(<HotTabledata={[{brand:'Jetpulse',model:'Racing Socks',price:30,sellDate:'Oct 11, 2023',sellTime:'01:23 AM',inStock:false,},{brand:'Gigabox',model:'HL Mountain Frame',price:1890.9,sellDate:'May 3, 2023',sellTime:'11:27 AM',inStock:false,},{brand:'Camido',model:'Cycling Cap',price:130.1,sellDate:'Mar 27, 2023',sellTime:'03:17 AM',inStock:true,},{brand:'Chatterpoint',model:'Road Tire Tube',price:59,sellDate:'Aug 28, 2023',sellTime:'08:01 AM',inStock:true,},{brand:'Eidel',model:'HL Road Tire',price:279.99,sellDate:'Oct 2, 2023',sellTime:'13:23 AM',inStock:true,},]}columns={[{title:'Brand',type:'text',data:'brand',},{title:'Model',type:'text',data:'model',},{title:'Price',type:'numeric',data:'price',numericFormat:{pattern:'$ 0,0.00',culture:'en-US',},},{title:'Date',type:'date',data:'sellDate',dateFormat:'MMM D, YYYY',correctFormat:true,className:'htRight',},{title:'Time',type:'time',data:'sellTime',timeFormat:'hh:mm A',correctFormat:true,correctFormat:true,className:'htRight',},{title:'In stock',type:'checkbox',data:'inStock',className:'htCenter',},]}// enable sorting for all columnscolumnSorting={true}height="auto"stretchH="all"licenseKey="non-commercial-and-evaluation"/>);};
Enable sorting
To enable sorting for all columns, set columnSorting to true.
<HotTable// enable sorting for all columnscolumnSorting={true}/>
To enable sorting only for specific columns, set headerAction to
false for those columns that you don't want to sort. In the following example, only columns Model, Date and In stock are sortable.
import{ HotTable }from'@handsontable/react';import{ registerAllModules }from'handsontable/registry';import'handsontable/dist/handsontable.full.min.css';// register Handsontable's modulesregisterAllModules();exportconstApp=()=>{return(<HotTabledata={[{brand:'Jetpulse',model:'Racing Socks',price:30,sellDate:'Oct 11, 2023',sellTime:'01:23 AM',inStock:false,},{brand:'Gigabox',model:'HL Mountain Frame',price:1890.9,sellDate:'May 3, 2023',sellTime:'11:27 AM',inStock:false,},{brand:'Camido',model:'Cycling Cap',price:130.1,sellDate:'Mar 27, 2023',sellTime:'03:17 AM',inStock:true,},{brand:'Chatterpoint',model:'Road Tire Tube',price:59,sellDate:'Aug 28, 2023',sellTime:'08:01 AM',inStock:true,},{brand:'Eidel',model:'HL Road Tire',price:279.99,sellDate:'Oct 2, 2023',sellTime:'13:23 AM',inStock:true,},]}// enable sorting for all columnscolumnSorting={true}columns={[{title:'Brand',type:'text',data:'brand',// disable sorting for the 'Brand' columncolumnSorting:{headerAction:false,},},{title:'Model',type:'text',data:'model',},{title:'Price',type:'numeric',data:'price',numericFormat:{pattern:'$ 0,0.00',culture:'en-US',},// disable sorting for the 'Price' columncolumnSorting:{headerAction:false,},},{title:'Date',type:'date',data:'sellDate',dateFormat:'MMM D, YYYY',correctFormat:true,className:'htRight',},{title:'Time',type:'time',data:'sellTime',timeFormat:'hh:mm A',correctFormat:true,className:'htRight',// disable sorting for the 'Time' columncolumnSorting:{headerAction:false,},},{title:'In stock',type:'checkbox',data:'inStock',className:'htCenter',},]}height="auto"stretchH="all"licenseKey="non-commercial-and-evaluation"/>);};
The end user can sort data by clicking on the column name.
The sort order indicator is visible.
At Handsontable's initialization, no rows are sorted.
You can configure the following options:
<HotTable
columnSorting={{// let the end user sort data by clicking on the column name (set by default)headerAction:true,// don't sort empty cells – move rows that contain empty cells to the bottom (set by default)sortEmptyCells:false,// enable the sort order icon that appears next to the column name (set by default)indicator:true,// at initialization, sort data by the first column, in descending orderinitialConfig:{column:1,sortOrder:'desc',},// implement your own comparatorcompareFunctionFactory(sortOrder, columnMeta){returnfunction(value, nextValue){// here, add a compare function// that returns `-1`, or `0`, or `1`},},}}/>
Sort different types of data
Handsontable sorts different types of data
automatically, based on which type you configure for each column.
You can configure the following types:
text gets sorted by default, so you don't have to configure
it.
import{ HotTable }from'@handsontable/react';import{ registerAllModules }from'handsontable/registry';import'handsontable/dist/handsontable.full.min.css';// register Handsontable's modulesregisterAllModules();exportconstApp=()=>{return(<HotTabledata={[{model:'Racing Socks',size:'S',price:30,sellDate:'Oct 11, 2023',sellTime:'01:23 AM',inStock:false,color:'Black',email:'8576@all.xyz',},{model:'HL Mountain Shirt',size:'XS',price:1890.9,sellDate:'May 3, 2023',sellTime:'11:27 AM',inStock:false,color:'White',email:'tayn@all.xyz',},{model:'Cycling Cap',size:'L',price:130.1,sellDate:'Mar 27, 2023',sellTime:'03:17 AM',inStock:true,color:'Green',email:'6lights@far.com',},{model:'Ski Jacket',size:'M',price:59,sellDate:'Aug 28, 2023',sellTime:'08:01 AM',inStock:true,color:'Blue',email:'raj@fq1my2c.com',},{model:'HL Goggles',size:'XL',price:279.99,sellDate:'Oct 2, 2023',sellTime:'13:23 AM',inStock:true,color:'Black',email:'da@pdc.ga',},]}columns={[{title:'Model<br>(text)',// set the type of the 'Model' columntype:'text',// 'text' is the default type, so you can omit this linedata:'model',},{title:'Price<br>(numeric)',// set the type of the 'Price' columntype:'numeric',data:'price',numericFormat:{pattern:'$ 0,0.00',culture:'en-US',},},{title:'Sold on<br>(date)',// set the type of the 'Date' columntype:'date',data:'sellDate',dateFormat:'MMM D, YYYY',correctFormat:true,className:'htRight',},{title:'Time<br>(time)',// set the type of the 'Time' columntype:'time',data:'sellTime',timeFormat:'hh:mm A',correctFormat:true,className:'htRight',},{title:'In stock<br>(checkbox)',// set the type of the 'In stock' columntype:'checkbox',data:'inStock',className:'htCenter',},{title:'Size<br>(dropdown)',// set the type of the 'Size' columntype:'dropdown',data:'size',source:['XS','S','M','L','XL'],className:'htCenter',},{title:'Color<br>(autocomplete)',// set the type of the 'Size' columntype:'autocomplete',data:'color',source:['White','Black','Yellow','Blue','Green'],className:'htCenter',},{title:'Email<br>(password)',// set the type of the 'Email' columntype:'password',data:'email',},]}columnSorting={true}height={168}stretchH="all"licenseKey="non-commercial-and-evaluation"/>);};
You can also create a custom type. For details, see this guide:
Cell type.
Sort by multiple columns
You can sort data by more than one column, which lets you apply multiple sets of sort criteria at
the same time.
To try out sorting by multiple columns, see the following demo:
Click on the Brand column name. The data gets sorted by brand.
Hold down Cmd/Ctrl and click on the Model column name. The
data gets sorted by model, but within each brand.
Hold down Cmd/Ctrl and click on the Price column name. The
data gets sorted by price, but within each model.
<HotTable// enable sorting by multiple columns, for all columnsmultiColumnSorting={true}/>
To select which columns can be sorted at the same time, set
headerAction to false for those columns that you don't want to
sort.
<HotTable// enable sorting by multiple columns, for all columnsmultiColumnSorting={true}columns={[{// disable sorting by multiple columns for the first columncolumnSorting:{headerAction:false,},},]}/>
The columnSorting and
multiColumnSorting options override each other. If you use
them both, the one defined later takes precedence.
Set an initial sort order
You can set a default sort order that's applied every time you initialize Handsontable.
In the following demo, the data is initially sorted:
By the Brand column, in ascending order
By the Model column, in descending order
import{ HotTable }from'@handsontable/react';import{ registerAllModules }from'handsontable/registry';import'handsontable/dist/handsontable.full.min.css';// register Handsontable's modulesregisterAllModules();exportconstApp=()=>{return(<HotTabledata={[{brand:'Jetpulse',model:'HL Mountain Frame',price:30,sellDate:'Oct 11, 2023',sellTime:'01:23 AM',inStock:false,},{brand:'Jetpulse',model:'HL Mountain Frame',price:1890.9,sellDate:'May 3, 2023',sellTime:'11:27 AM',inStock:false,},{brand:'Jetpulse',model:'Cycling Cap',price:130.1,sellDate:'Mar 27, 2023',sellTime:'03:17 AM',inStock:true,},{brand:'Chatterpoint',model:'Road Tire Tube',price:59,sellDate:'Aug 28, 2023',sellTime:'08:01 AM',inStock:true,},{brand:'Chatterpoint',model:'HL Road Tire',price:279.99,sellDate:'Oct 2, 2023',sellTime:'13:23 AM',inStock:true,},]}columns={[{title:'Brand',type:'text',data:'brand',},{title:'Model',type:'text',data:'model',},{title:'Price',type:'numeric',data:'price',numericFormat:{pattern:'$ 0,0.00',culture:'en-US',},},{title:'Date',type:'date',data:'sellDate',dateFormat:'MMM D, YYYY',correctFormat:true,className:'htRight',},{title:'Time',type:'time',data:'sellTime',timeFormat:'hh:mm A',correctFormat: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 ordercolumn:0,sortOrder:'asc',},// at initialization, sort the data by the 'Model' column, in descending order{column:1,sortOrder:'desc',},],}}height="auto"stretchH="all"licenseKey="non-commercial-and-evaluation"/>);};
To set an initial sort order, use the initialConfig option.
<HotTablecolumnSorting={{// at initialization, sort data by the first column, in ascending orderinitialConfig:{column:0,sortOrder:'asc',// for descending order, use `'desc'`},}}/>
<HotTable// enable sorting by multiple columnsmultiColumnSorting={{initialConfig:[// at initialization, sort data by the first column, in ascending order{column:0,sortOrder:'asc',},// at initialization, sort data by the second column, in descending order{column:1,sortOrder:'desc',},],}}/>
Add custom sort icons
The default sort icons (↑↓) are encoded in Base64. You can replace them by changing
background-image for the following pseudo-elements of Handsontable's CSS:
/* the icon for both ascending and descending order */.custom-sort-icon-example-1 span.colHeader.columnSorting.ascending::before,
.custom-sort-icon-example-1 span.colHeader.columnSorting.descending::before{background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pg0KPCEtLSBVcGxvYWRlZCB0bzogU1ZHIFJlcG8sIHd3dy5zdmdyZXBvLmNvbSwgR2VuZXJhdG9yOiBTVkcgUmVwbyBNaXhlciBUb29scyAtLT4NCjxzdmcgZmlsbD0iIzAwMDAwMCIgaGVpZ2h0PSI4MDBweCIgd2lkdGg9IjgwMHB4IiB2ZXJzaW9uPSIxLjEiIGlkPSJMYXllcl8xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiANCgkgdmlld0JveD0iMCAwIDMzMCAzMzAiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPHBhdGggaWQ9IlhNTElEXzIyNV8iIGQ9Ik0zMjUuNjA3LDc5LjM5M2MtNS44NTctNS44NTctMTUuMzU1LTUuODU4LTIxLjIxMywwLjAwMWwtMTM5LjM5LDEzOS4zOTNMMjUuNjA3LDc5LjM5Mw0KCWMtNS44NTctNS44NTctMTUuMzU1LTUuODU4LTIxLjIxMywwLjAwMWMtNS44NTgsNS44NTgtNS44NTgsMTUuMzU1LDAsMjEuMjEzbDE1MC4wMDQsMTUwYzIuODEzLDIuODEzLDYuNjI4LDQuMzkzLDEwLjYwNiw0LjM5Mw0KCXM3Ljc5NC0xLjU4MSwxMC42MDYtNC4zOTRsMTQ5Ljk5Ni0xNTBDMzMxLjQ2NSw5NC43NDksMzMxLjQ2NSw4NS4yNTEsMzI1LjYwNyw3OS4zOTN6Ii8+DQo8L3N2Zz4=)!important;/* minor adjustments, as the custom icon is of a different size than the original */top: 12px;right: -35px;width: 22px;height: 22px;zoom: 0.4;}/* the same icon as for ascending order, but rotated 180 degrees */.custom-sort-icon-example-1 span.colHeader.columnSorting.descending:before{transform:scaleY(-1);}
You can also replace the sort icons by changing content for the same pseudo-elements:
/* the icon for ascending order */.custom-sort-icon-example-2 span.colHeader.columnSorting.ascending::before{content:'△';background-image: none !important;}/* the icon for descending order */.custom-sort-icon-example-2 span.colHeader.columnSorting.descending::before{content:'▽';background-image: none !important;}
To replace the icons that indicate sorting by multiple columns
(1, 2 etc.), change content for the .columnSorting.sort-1::after and
subsequent pseudo-elements:
A comparator is a function that determines the sort order, based on specified criteria.
Adding a custom comparator lets you go beyond Handsontable's built-in sorting features. You can:
Apply a custom sort order. For example, instead of sorting data alphabetically or numerically, you
can sort it by length or by the occurrence of a specific character.
Handle exceptions. For example, in a list of employees, you can exclude workers with a specific
job title from sorting.
Implement a custom sorting logic based on your own criteria.
<HotTablebeforeColumnSort={// add your code herereturnfalse;// to block front-end sorting}afterColumnSort={// add your code here}/>
Exclude rows from sorting
You can exclude any number of top or bottom rows from sorting.
For example, if you freeze a row at the top (to display column
names), and freeze a row at the bottom (to display
column summaries), you can prevent those frozen rows from
getting sorted, so they always stay in place.
// you need `useRef` to call Handsontable's instance methodsimport{ useRef }from'react';import{ HotTable }from'@handsontable/react';import{ registerAllModules }from'handsontable/registry';import'handsontable/dist/handsontable.full.min.css';// register Handsontable's modulesregisterAllModules();exportconstApp=()=>{const hotTableComponentRef =useRef(null);constexclude=()=>{const handsontableInstance = hotTableComponentRef.current.hotInstance;const lastRowIndex = handsontableInstance.countRows()-1;// after each sorting, take row 1 and change its index to 0
handsontableInstance.rowIndexMapper.moveIndexes(handsontableInstance.toVisualRow(0),0);// after each sorting, take row 16 and change its index to 15
handsontableInstance.rowIndexMapper.moveIndexes(
handsontableInstance.toVisualRow(lastRowIndex),
lastRowIndex
);};return(<HotTable
ref={hotTableComponentRef}
data={[{brand:'Brand',model:'Model',price:'Price',sellDate:'Date',sellTime:'Time',inStock:'In stock',},{brand:'Gigabox',model:'HL Mountain Frame',price:1890.9,sellDate:'May 3, 2023',sellTime:'11:27 AM',inStock:11,},{brand:'Camido',model:'Cycling Cap',price:130.1,sellDate:'Mar 27, 2023',sellTime:'03:17 AM',inStock:0,},{brand:'Chatterpoint',model:'Road Tire Tube',price:59,sellDate:'Aug 28, 2023',sellTime:'08:01 AM',inStock:1,},{brand:'Eidel',model:'HL Road Tire',price:279.99,sellDate:'Oct 2, 2023',sellTime:'13:23 AM',inStock:3,},{brand:'Jetpulse',model:'Racing Socks',price:30,sellDate:'Oct 11, 2023',sellTime:'01:23 AM',inStock:5,},{brand:'Gigabox',model:'HL Mountain Frame',price:1890.9,sellDate:'May 3, 2023',sellTime:'11:27 AM',inStock:22,},{brand:'Camido',model:'Cycling Cap',price:130.1,sellDate:'Mar 27, 2023',sellTime:'03:17 AM',inStock:13,},{brand:'Chatterpoint',model:'Road Tire Tube',price:59,sellDate:'Aug 28, 2023',sellTime:'08:01 AM',inStock:0,},{brand:'Eidel',model:'HL Road Tire',price:279.99,sellDate:'Oct 2, 2023',sellTime:'13:23 AM',inStock:14,},{brand:'Jetpulse',model:'Racing Socks',price:30,sellDate:'Oct 11, 2023',sellTime:'01:23 AM',inStock:16,},{brand:'Gigabox',model:'HL Mountain Frame',price:1890.9,sellDate:'May 3, 2023',sellTime:'11:27 AM',inStock:18,},{brand:'Camido',model:'Cycling Cap',price:130.1,sellDate:'Mar 27, 2023',sellTime:'03:17 AM',inStock:3,},{brand:'Chatterpoint',model:'Road Tire Tube',price:59,sellDate:'Aug 28, 2023',sellTime:'08:01 AM',inStock:0,},{brand:'Vinte',model:'ML Road Frame-W',price:30,sellDate:'Oct 11, 2023',sellTime:'01:23 AM',inStock:2,},{},]}
columns={[{type:'text',data:'brand',},{type:'text',data:'model',},{type:'numeric',data:'price',numericFormat:{pattern:'$ 0,0.00',culture:'en-US',},},{type:'date',data:'sellDate',dateFormat:'MMM D, YYYY',correctFormat:true,className:'htRight',},{type:'time',data:'sellTime',timeFormat:'hh:mm A',correctFormat: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={exclude}
cells={(row, col, prop)=>{if(hotTableComponentRef.current !=null){const lastRowIndex = hotTableComponentRef.current.hotInstance.countRows()-1;if(row ===0){return{type:'text',className:'htCenter',readOnly:true,};}if(row === lastRowIndex){return{type:'numeric',className:'htCenter',};}}}}
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,},]}
licenseKey="non-commercial-and-evaluation"/>);};
Control sorting programmatically
You can control sorting at the grid's runtime by using Handsontable's
hooks and
API methods.
This allows you to:
Enable or disable sorting depending on specified conditions. For example, you can disable sorting
for very large data sets.
Trigger sorting depending on the state of another component in your application. For example, you
can let the end user sort data by clicking on buttons outside of the grid.
To learn how to access Handsontable's API methods, see this guide:
Instance methods.
Enable or disable sorting programmatically
To enable or disable sorting programmatically, use the
updateSettings() method.
const hotTableComponentRef =useRef(null);// enable sorting for all columns
hotTableComponentRef.current.hotInstance.updateSettings({columnSorting:true,});// disable sorting for all columns
hotTableComponentRef.current.hotInstance.updateSettings({columnSorting:false,});
You can also enable or disable sorting for specific columns.
const hotTableComponentRef =useRef(null);
hotTableComponentRef.current.hotInstance.updateSettings({columns:[{// enable sorting for the first columncolumnSorting:{headerAction:true,},},{// disable sorting for the second columncolumnSorting:{headerAction:false,},},],});
<HotTable// enable sorting for all columnscolumnSorting={true}ref={hotTableComponentRef}/>;const hotTableComponentRef =useRef(null);// get the `ColumnSorting` pluginconst columnSorting = hotTableComponentRef.current.hotInstance.getPlugin('columnSorting');
columnSorting.sort(// sort data by the first column, in ascending order{column:0,sortOrder:'asc',// for descending order, use `'desc'`});// go back to the original order
columnSorting.clearSort();
To see how it works, try out the following demo:
import{ useRef }from'react';import{ HotTable }from'@handsontable/react';import{ registerAllModules }from'handsontable/registry';import'handsontable/dist/handsontable.full.min.css';// register Handsontable's modulesregisterAllModules();exportconstApp=()=>{const hotTableComponentRef =useRef(null);constsortAsc=()=>{// get the `ColumnSorting` pluginconst columnSorting = hotTableComponentRef.current.hotInstance.getPlugin('columnSorting');
columnSorting.sort({column:0,sortOrder:'asc',});};constunsort=()=>{// get the `ColumnSorting` pluginconst columnSorting = hotTableComponentRef.current.hotInstance.getPlugin('columnSorting');
columnSorting.clearSort();};return(<><HotTableref={hotTableComponentRef}data={[{brand:'Jetpulse',model:'Racing Socks',price:30,sellDate:'Oct 11, 2023',sellTime:'01:23 AM',inStock:false,},{brand:'Gigabox',model:'HL Mountain Frame',price:1890.9,sellDate:'May 3, 2023',sellTime:'11:27 AM',inStock:false,},{brand:'Camido',model:'Cycling Cap',price:130.1,sellDate:'Mar 27, 2023',sellTime:'03:17 AM',inStock:true,},{brand:'Chatterpoint',model:'Road Tire Tube',price:59,sellDate:'Aug 28, 2023',sellTime:'08:01 AM',inStock:true,},{brand:'Eidel',model:'HL Road Tire',price:279.99,sellDate:'Oct 2, 2023',sellTime:'13:23 AM',inStock:true,},]}columns={[{title:'Brand',type:'text',data:'brand',},{title:'Model',type:'text',data:'model',},{title:'Price',type:'numeric',data:'price',numericFormat:{pattern:'$ 0,0.00',culture:'en-US',},},{title:'Date',type:'date',data:'sellDate',dateFormat:'MMM D, YYYY',correctFormat:true,className:'htRight',},{title:'Time',type:'time',data:'sellTime',timeFormat:'hh:mm A',correctFormat:true,className:'htRight',},{title:'In stock',type:'checkbox',data:'inStock',className:'htCenter',},]}columnSorting={true}height="auto"stretchH="all"licenseKey="non-commercial-and-evaluation"/><divclassName="controls"><buttononClick={sortAsc}>Sort by the "Brand" column, in ascending order</button><br/><br/><buttononClick={unsort}>Go back to the original order</button></div></>);};
<HotTable// enable sorting by multiple columns, for all columnsmultiColumnSorting={true}ref={hotTableComponentRef}/>;const hotTableComponentRef =useRef(null);// get the `ColumnSorting` pluginconst multiColumnSorting = hotTableComponentRef.current.hotInstance.getPlugin('multiColumnSorting');
multiColumnSorting.sort([// sort data by the first column, in ascending order{column:0,sortOrder:'asc',},// within the above sort criteria,// sort data by the second column, in descending order{column:1,sortOrder:'desc',},]);// go back to the original order
multiColumnSorting.clearSort();