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 Handsontable from'handsontable';import'handsontable/dist/handsontable.full.min.css';const container = document.querySelector('#exampleSortingDemo');const handsontableInstance =newHandsontable(container,{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',},],// enable sorting for all columnscolumnSorting:true,height:'auto',stretchH:'all',autoWrapRow:true,autoWrapCol:true,licenseKey:'non-commercial-and-evaluation',});
<divid="exampleSortingDemo"></div>
Enable sorting
To enable sorting for all columns, set columnSorting to true.
const configurationOptions ={// 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 Handsontable from'handsontable';import'handsontable/dist/handsontable.full.min.css';const container = document.querySelector('#exampleEnableSortingForColumns');const handsontableInstance =newHandsontable(container,{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,},],// 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',autoWrapRow:true,autoWrapCol:true,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:
const configurationOptions ={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 Handsontable from'handsontable';import'handsontable/dist/handsontable.full.min.css';const container = document.querySelector('#exampleSortDifferentTypes');const handsontableInstance =newHandsontable(container,{data:[{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',autoWrapRow:true,autoWrapCol:true,licenseKey:'non-commercial-and-evaluation',});
<divid="exampleSortDifferentTypes"></div>
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.
const configurationOptions ={// 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.
const configurationOptions ={// enable sorting by multiple columns, for all columnsmultiColumnSorting:true,columns:[{// disable sorting by multiple columns for the first columnmultiColumnSorting:{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 Handsontable from'handsontable';import'handsontable/dist/handsontable.full.min.css';const container = document.querySelector('#exampleInitialSortOrder');const handsontableInstance =newHandsontable(container,{data:[{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 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',});
<divid="exampleInitialSortOrder"></div>
To set an initial sort order, use the initialConfig option.
const configurationOptions ={columnSorting:{// at initialization, sort data by the first column, in ascending orderinitialConfig:{column:0,sortOrder:'asc',// for descending order, use `'desc'`},};
const configurationOptions ={// 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.
const configurationOptions ={beforeColumnSort(){// 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.
import Handsontable from'handsontable';import'handsontable/dist/handsontable.full.min.css';const container = document.querySelector('#exampleExcludeRowsFromSorting');const handsontableInstance =newHandsontable(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:'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 sortingafterColumnSort(){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);},cells(row, col, prop){const lastRowIndex =this.instance.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,},],autoWrapRow:true,autoWrapCol:true,licenseKey:'non-commercial-and-evaluation',});
<divid="exampleExcludeRowsFromSorting"></div>
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.
Enable or disable sorting programmatically
To enable or disable sorting programmatically, use the updateSettings() method.
// enable sorting for all columns
handsontableInstance.updateSettings({columnSorting:true,});// disable sorting for all columns
handsontableInstance.updateSettings({columnSorting:false,});
You can also enable or disable sorting for specific columns.
handsontableInstance.updateSettings({columns:[{// enable sorting for the first columncolumnSorting:{headerAction:true,},},{// disable sorting for the second columncolumnSorting:{headerAction:false,},},],});
const configurationOptions ={// enable sorting for all columnscolumnSorting:true,};const columnSorting = handsontableInstance.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();
<divid="exampleSortByAPI"></div><divclass="controls"><buttonid="sort_asc"class="button">Sort by the "Brand" column, in ascending order</button><br/><br/><buttonid="unsort"class="button">Go back to the original order</button></div>
const configurationOptions ={// enable sorting by multiple columns, for all columnsmultiColumnSorting:true,};// get the `MultiColumnSorting` pluginconst multiColumnSorting = handsontableInstance.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();