Recommended Free Tools
iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more
To sort an HTML table when a reader clicks a column heading, collect the rows from its <tbody>, compare each row’s value in that column, then append the rows back in sorted order. The example below adds ascending and descending sorting for text and numeric columns, with keyboard-operable buttons and an accessible sort state.
Set up the table with sortable headers
Keep column headings in <thead> and data rows in <tbody>. Put a real button inside each sortable header so it can be activated with a keyboard as well as a pointer.
<table>
<caption>Scores. Activate a column heading to sort the table.</caption>
<thead>
<tr>
<th scope="col"><button type="button" data-sort="name">Name</button></th>
<th scope="col"><button type="button" data-sort="score">Score</button></th>
</tr>
</thead>
<tbody>
<tr><td>Alex</td><td>15</td></tr>
<tr><td>Devlin</td><td>12</td></tr>
<tr><td>Sam</td><td>18</td></tr>
</tbody>
</table>
The data-sort values identify each column’s comparison type in the script. Keep them aligned with the comparison logic you add. The sample expects one simple header row, with header positions matching data-cell positions.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Fix the driver behind crashes, sound loss and screen glitches3Repair Windows errors before they cause bigger problemsAdd the sorting behavior
Place this script after the table or run it after the document has loaded. It uses a regular array of the body’s rows, sorts that array, and then moves the existing row elements into the new order.
#1 Best Overall
const table = document.querySelector("table");
const tbody = table.tBodies[0];
const buttons = table.querySelectorAll("thead button[data-sort]");
let activeColumn = null;
let ascending = true;
for (const button of buttons) {
button.addEventListener("click", () => {
const key = button.dataset.sort;
const header = button.closest("th");
const columnIndex = Array.from(header.parentElement.cells).indexOf(header);
const rows = Array.from(tbody.rows);
if (activeColumn === key) {
ascending = !ascending;
} else {
activeColumn = key;
ascending = true;
}
rows.sort((rowA, rowB) => {
const a = rowA.cells[columnIndex].textContent.trim();
const b = rowB.cells[columnIndex].textContent.trim();
const result = key === "score"
? Number(a) - Number(b)
: a.localeCompare(b);
return ascending ? result : -result;
});
tbody.append(...rows);
for (const cell of table.tHead.rows[0].cells) {
cell.removeAttribute("aria-sort");
}
header.setAttribute("aria-sort", ascending ? "ascending" : "descending");
});
}
- Find the table elements.
table.tBodies[0]selects the first body section, and the query selects the header buttons. - Get the column position. The script finds the clicked button’s header cell, then gets that cell’s position in its header row.
- Choose the direction. Clicking the active column reverses its direction; selecting a different column starts ascending.
- Compare and reorder. The comparison uses numeric subtraction for scores and
localeCompare()for names.tbody.append(...rows)moves the sorted row elements into their new positions. - Expose the current state. The active header receives
aria-sort; the other headers have the attribute removed.
To show direction visually, add an arrow or other indicator to the active button and update it in the click handler. The visual cue should agree with the aria-sort value.
Choose a comparator that matches the data
JavaScript’s Array.prototype.sort() sorts an array in place. Without a comparator, it converts values to strings and orders them lexically; for example, numbers such as 2 and 10 will not sort in numeric order. Use a comparator that returns a negative value when the first item comes before the second, a positive value when it comes after, and zero when they compare equal. MDN explains the method’s behavior and comparator requirements in its Array.prototype.sort() reference.
Rank #2
- Text:
a.localeCompare(b)is a straightforward option for comparing text. - Numbers: Compare numeric values with subtraction, as in
Number(a) - Number(b), only when the cell text is a plain number. - Formatted or missing values: Normalize the underlying value before comparison. Handle blanks and invalid values explicitly rather than relying on accidental conversions.
- Dates: Compare a normalized date value, not a display string whose format may sort differently from chronological order.
A comparator should apply consistent ordering rules. Returning only 1 and 0, for example, fails to distinguish both directions of a comparison and can produce inconsistent results. Since ECMAScript 2019, the specification requires stable sorting: rows that compare equal retain their prior relative order. MDN notes that the specification does not guarantee a particular time or space complexity for sort().
Make the sort state accessible
Use semantic table markup, including <th scope="col">, and make each sortable heading a button. The button communicates that the heading is an interactive control; aria-sort="ascending" or aria-sort="descending" on the currently sorted header communicates the direction to assistive technology. Keep the attribute on only the active heading, and use a visible direction indicator as well. See the W3C WAI-ARIA APG sortable-table example and MDN’s guidance on aria-sort.
The table caption in the example explains that headings can be activated to sort. Test the interaction with keyboard navigation and assistive technology; a visual arrow alone does not communicate the state to every user.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Adapt the example to your table
This compact script is suitable for a simple table with one header row and plain text or numeric cells. Adjust the approach when the table has richer structure or data:
Rank #4
- Blank, invalid, or localized values: Decide where these belong and parse them according to the format before comparing.
Number()does not automatically interpret every locale’s decimal or thousands separators. - Links or nested markup in cells:
textContentreads the text, but it may not be the value you intend to sort. Store a normalized value in a data attribute or use the underlying data instead. - Multiple body sections: The example sorts only the first
<tbody>. Decide whether each group should sort independently or whether the data model should be sorted before rendering. - Spanning or multi-row headers: A header’s position may not match its data column when cells use
colspanorrowspan. Use an explicit mapping from each button to its data column. - Server-paginated or large data sets: Sorting the rows currently in the DOM only reorders those rows. If other records are on another page or held by a server, sort the underlying complete data set or request a sorted page from the data source.
MDN’s HTML table-row reference also demonstrates click-driven row sorting; its simple example assumes plain text and the first body section, so more complex tables need an adapted column mapping and data strategy.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Quick Recap
Best Value
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

