Sortable Table Web Component Demo

A web component to enable users to sort table data by clicking on column headers. Built with accessibility in mind, supporting keyboard navigation and native sort semantics. See the README for installation and API documentation.

Progressive Enhancement

The component automatically enhances plain table headers by injecting buttons. You don't need to add buttons or links to your markup. This example uses decorative SVG caret pairs: the active direction is filled and the other direction remains outlined.

Auto-Enhanced Table

<table-sortable class="svg-sort-indicators">
  <table>
    <thead>
      <tr>
        <th>Name</th>
        <th>Age</th>
        <th>City</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>Charlie Brown</td>
        <td>35</td>
        <td>New York</td>
      </tr>
      <!-- more rows -->
    </tbody>
  </table>
</table-sortable>
Name Age City Occupation
Charlie Brown 35 New York Designer
Alice Cooper 28 Boston Developer
Bob Dylan 42 Chicago Manager
Diana Prince 31 Seattle Engineer
Eve Anderson 26 Portland Designer
Progressive Enhancement: The component automatically creates sortable buttons inside each <th> element. No need to add buttons or links manually!

Custom Sort Keys

Use the data-sort-value attribute to define custom sort values that differ from the displayed content.

Product Pricing with Custom Keys

<tr>
  <td data-sort-value="WIDGET-B">Widget B (Premium)</td>
  <td>WDG-002</td>
  <td data-sort-value="50">$50.00</td>
  <td data-sort-value="15">Low (15)</td>
</tr>
Product SKU Price Stock
Widget B (Premium) WDG-002 $50.00 Low (15)
Widget A (Basic) WDG-001 $30.00 High (100)
Widget C (Deluxe) WDG-003 $100.00 Very Low (5)
Gadget A (Standard) GDG-001 $75.00 Medium (50)
Note: The "Price" and "Stock" columns use data-sort-value to sort numerically rather than alphabetically by the formatted text.

Inline Sort Keys

Wrap the visible text fragment you want to prioritize for sorting with [data-sort-as].

Sorting by Last Name

<td>
  John <span data-sort-as>Smith</span>
</td>
Full Name Email Department
John Smith john.smith@example.com Engineering
Emily Anderson emily.anderson@example.com Marketing
Wei Zhang wei.zhang@example.com Design
Sarah Brown sarah.brown@example.com Sales
Note: The name column sorts by last name (indicated using [data-sort-as]) while displaying "First Last" format. Hidden sort tokens are optional, not required.

Grouped Tables

Add the same data-table-sort-group value to every <tr> in a group to keep those rows together while sorting. Alternatively, put the attribute on a <tbody> to apply that value to all of its rows.

Department-Grouped Employee List

<tbody>
  <tr data-table-sort-group="engineering">
    <td>Charlie Brown</td>
    <td>85</td>
    <td>Active</td>
  </tr>
  <tr data-table-sort-group="engineering">
    <td>Frank Miller</td>
    <td>88</td>
    <td>On Leave</td>
  </tr>
  <tr data-table-sort-group="design">
    <td>Alice Cooper</td>
    <td>92</td>
    <td>Active</td>
  </tr>
</tbody>
Name Score Status
Charlie Brown (Engineering) 85 Active
Frank Miller (Engineering) 88 On Leave
Eve Adams (Engineering) 76 Inactive
Alice Cooper (Design) 92 Active
Grace Hopper (Design) 98 Active
Bob Dylan (Sales) 78 Active
Henry Ford (Sales) 84 Active
Diana Prince (Marketing) 95 Active
Choosing where to define a group: Apply data-table-sort-group to each <tr> when, as in this example, several groups share one <tbody>. Alternatively, apply it to a <tbody> to give all of that body's rows the same group. A value directly on a row overrides the body's value. When sorting, the component rebuilds the <tbody> elements from these effective group values; the original body structure alone does not define groups. All rows retain the same neutral dark/light striping. In the sorted column, odd bodies use dark/light purple cells and even bodies use dark/light green cells.

Custom Labels (Localization)

Use label-sortable to describe unsorted column buttons. Use label-ascending and label-descending to describe the active column and the next available action.

French Localization

<table-sortable
  label-sortable="Cliquer pour trier par ordre croissant"
  label-ascending="trié croissant. Cliquer pour trier décroissant"
  label-descending="trié décroissant. Cliquer pour trier croissant">
  <table>
    <thead>
      <tr>
        <th>Nom</th>
        <th>Âge</th>
      </tr>
    </thead>
    <!-- ... -->
  </table>
</table-sortable>
Nom Âge Ville
Marie 28 Paris
Pierre 35 Lyon
Sophie 42 Marseille
Note: Each sort button references the localized description for its current state and next action. The active header also exposes its direction through aria-sort.

Progressive Enhancement

The component uses pure progressive enhancement. Simply write plain <th> elements in your table headers, and the component automatically injects accessible <button> elements to make them sortable. You don't need to add any buttons or links manually.

All examples on this page demonstrate this approach - just plain <th> elements that get enhanced with sorting functionality.

Event Handling

Listen to the table-sortable:sort event to react to sorting changes.

Sort Event Listener

element.addEventListener('table-sortable:sort', (event) => {
  const { column, direction, header } = event.detail;
  console.log(`Sorted by ${header.textContent}`);
});
Name Value
Item C 30
Item A 10
Item B 20
Event Log:
Click a column header to see events...

API Reference

For complete documentation of attributes, events, and browser support, see the README.