Skip to content

Latest commit

 

History

History
310 lines (238 loc) · 9.61 KB

File metadata and controls

310 lines (238 loc) · 9.61 KB

Sortable Table

sortable -- Enable Table Sorting

Add to a <table> to enable column sorting. Works with the each directive in <tbody> -- sorting mutates the data array in context, and each automatically re-renders the rows.

<div state="{ users: [
  { name: 'Alice', age: 30, email: 'alice@example.com' },
  { name: 'Bob', age: 25, email: 'bob@example.com' },
  { name: 'Carol', age: 35, email: 'carol@example.com' }
] }">
  <table sortable>
    <thead>
      <tr>
        <th sort="name">Name</th>
        <th sort="age" sort-type="number">Age</th>
        <th sort="email">Email</th>
        <th>Actions</th> <!-- no sort = not sortable -->
      </tr>
    </thead>
    <tbody>
      <tr each="user in users">
        <td bind="user.name"></td>
        <td bind="user.age"></td>
        <td bind="user.email"></td>
        <td><button on:click="alert(user.name)">View</button></td>
      </tr>
      <tr else>
        <td colspan="4">No users found</td>
      </tr>
    </tbody>
  </table>
</div>

The each directive is placed on the <tr> element itself -- the row is the repeating template. The sibling <tr else> displays when the array is empty.

Sortable Attributes

Attribute Type Default Description
sortable boolean attr -- Enables sort coordination on the table. Adds .nojs-sortable class

sort -- Sortable Column

Place on a <th> inside a sortable table. The attribute value is the property key used for sorting.

<!-- String sort (default) -->
<th sort="name">Name</th>

<!-- Numeric sort -->
<th sort="age" sort-type="number">Age</th>

<!-- Date sort -->
<th sort="createdAt" sort-type="date">Created</th>

<!-- Default sort direction on load -->
<th sort="name" sort-default="asc">Name</th>
<th sort="score" sort-type="number" sort-default="desc">Score</th>

Sort Attributes

Attribute Type Default Description
sort string required Property key to sort by (e.g., "name", "age")
sort-type "string" | "number" | "date" "string" Comparator type. string uses localeCompare, number uses numeric comparison, date parses with new Date()
sort-default "asc" | "desc" -- Initial sort direction applied on page load

Sort Cycling

Clicking a column header cycles through three states:

  1. Ascending -- data-sort-dir="asc", displays ▲
  2. Descending -- data-sort-dir="desc", displays ▼
  3. None -- removes data-sort-dir, restores original array order

Clicking a different column resets the previous column and starts at ascending.

Typed Sorting

sort-type Behavior
"string" String(a).localeCompare(String(b)) -- locale-aware alphabetical
"number" Number(a) - Number(b) -- numeric comparison
"date" new Date(a).getTime() - new Date(b).getTime() -- chronological

null and undefined values sort before all other values.


fixed-header -- Sticky Table Header

Place on <thead> to make it stick to the top of its scroll container.

<div style="height: 400px; overflow: auto">
  <table sortable>
    <thead fixed-header>
      <tr>
        <th sort="name">Name</th>
        <th sort="age" sort-type="number">Age</th>
      </tr>
    </thead>
    <tbody>
      <tr each="user in users">
        <td bind="user.name"></td>
        <td bind="user.age"></td>
      </tr>
    </tbody>
  </table>
</div>
Attribute Type Default Description
fixed-header boolean attr -- Applies position: sticky; top: 0 to the <thead>

fixed-col -- Sticky Column

Place on <th> and/or <td> elements to pin a column to the left edge during horizontal scrolling.

<div style="width: 600px; overflow: auto">
  <table sortable>
    <thead fixed-header>
      <tr>
        <th sort="name" fixed-col>Name</th>
        <th sort="age" sort-type="number">Age</th>
        <th sort="email">Email</th>
        <th sort="department">Department</th>
      </tr>
    </thead>
    <tbody>
      <tr each="user in users">
        <td fixed-col bind="user.name"></td>
        <td bind="user.age"></td>
        <td bind="user.email"></td>
        <td bind="user.department"></td>
      </tr>
    </tbody>
  </table>
</div>
Attribute Type Default Description
fixed-col boolean attr -- Applies position: sticky; left: 0 to the element

When fixed-header and fixed-col intersect (top-left cell), z-index stacking is handled automatically (z-index: 3) so the cell stays above both the scrolling header and column.


CSS Classes

No.JS automatically injects these classes:

Class When applied
.nojs-sortable On the <table> with sortable
.nojs-fixed-header On the <thead> with fixed-header
.nojs-fixed-col On each <th>/<td> with fixed-col

Data Attributes on <th>

Attribute Values Description
data-sortable (presence) Marks a <th> as sortable (set automatically by sort)
data-sort-dir "asc" | "desc" Current sort direction on the active column. Removed when unsorted

Sort Indicator Styles

Sort indicators are rendered via CSS ::after pseudo-elements:

State ::after content Opacity
Unsorted ⇵ (up-down arrows) 0.3
Ascending ▲ (up triangle) 1
Descending ▼ (down triangle) 1

Z-Index Stacking

Element z-index
.nojs-fixed-col 1
.nojs-fixed-header 2
.nojs-fixed-header .nojs-fixed-col 3

Accessibility

No.JS automatically adds:

  • aria-sort="none" on each <th> with sort (initial state)
  • aria-sort="ascending" when sorted ascending
  • aria-sort="descending" when sorted descending
  • data-sortable attribute for styling hooks
  • cursor: pointer and user-select: none on sortable headers

Sorting is triggered by clicking column headers. Only one column can be sorted at a time -- clicking a new column resets all others to aria-sort="none".

How Sorting Works

The sort directive does not reorder DOM nodes. Instead, it mutates the data array in the NoJS context. The each directive detects the change and re-renders the <tbody> rows automatically. When sort is reset to "none", the original array order (captured before the first sort) is restored.


Row Reorder (DnD)

Requires NoJS Elements with the DnD module. See the migration guide if upgrading from Core-only DnD.

Add table-reorder to a <table> to enable drag-and-drop row reordering. Works with each-bound tables — reordering mutates the backing data array in context, and each automatically re-renders the rows.

<div state="{ users: [
  { id: 1, name: 'Alice', role: 'Admin' },
  { id: 2, name: 'Bob', role: 'Editor' },
  { id: 3, name: 'Carol', role: 'Viewer' }
] }">
  <table sortable table-reorder>
    <thead>
      <tr>
        <th sort="name">Name</th>
        <th sort="role">Role</th>
      </tr>
    </thead>
    <tbody>
      <tr each="user in users">
        <td bind="user.name"></td>
        <td bind="user.role"></td>
      </tr>
    </tbody>
  </table>
</div>

Reorder Attributes

Attribute Type Default Description
table-reorder boolean attr required Enables drag-and-drop row reordering on the table
table-reorder-handle CSS selector Restricts the drag handle to a child element within each row (e.g., ".grip")
table-reorder-drag-class string "nojs-row-dragging" CSS class applied to the row being dragged
table-reorder-over-class string "nojs-row-drag-over" CSS class applied to rows during hover

Handle Example

<table sortable table-reorder table-reorder-handle=".grip">
  <thead>
    <tr>
      <th></th>
      <th sort="name">Name</th>
      <th sort="role">Role</th>
    </tr>
  </thead>
  <tbody>
    <tr each="user in users">
      <td><span class="grip" style="cursor: grab">&#x2630;</span></td>
      <td bind="user.name"></td>
      <td bind="user.role"></td>
    </tr>
  </tbody>
</table>

Events

Event $event.detail Description
table:reorder { from, to, item } Dispatched on the <table> after a row is reordered. from and to are array indices, item is the moved data object
<table sortable table-reorder
       on:table:reorder="console.log('Moved from', $event.detail.from, 'to', $event.detail.to)">
  ...
</table>

Interaction with Column Sort

When a table has both sortable and table-reorder:

  • Reordering updates the backing array and invalidates the sort's original-order cache
  • If a column sort is active, dragging reorders the visually sorted rows and the new order persists in the data array
  • Resetting sort (clicking the sorted column a third time) restores the new reordered array, not the pre-drag order

CSS Classes

Class When applied
.nojs-row-dragging On the <tr> being dragged
.nojs-reorder-insert-before On a <tr> showing an insertion line above
.nojs-reorder-insert-after On a <tr> showing an insertion line below

Accessibility

  • draggable="true" is set on each <tr> in the tbody
  • aria-grabbed="true/false" reflects the drag state on each row

Previous: Split / Pane ←