Agent Wranglers

Lists and tables Required

Make sortable columns buttons

Put a button inside each sortable column header, and set aria-sort on the header of the sorted column only.

How to do it

Start from table headers. The button's name is the column name. Set aria-sort="ascending" or "descending" on the sorted th and remove it from the others. Show the direction with an icon as well as the attribute. Announce the new order in a status region, such as "Sorted by amount, highest first".

Example

<th scope="col" aria-sort="descending">
  <button type="button" data-sort="amount">Amount <svg aria-hidden="true" class="sort-icon">…</svg></button>
</th>
<th scope="col"><button type="button" data-sort="customer">Customer</button></th>
export function markSorted(table: HTMLTableElement, column: string, direction: 'ascending' | 'descending') {
  for (const header of table.querySelectorAll('th')) header.removeAttribute('aria-sort');
  table.querySelector(`button[data-sort="${column}"]`)?.closest('th')?.setAttribute('aria-sort', direction);
}
from playwright.sync_api import expect

def test_sort_by_keyboard(page):
    page.goto("https://staging.example.com/orders")
    page.get_by_role("button", name="Amount").focus()
    page.keyboard.press("Enter")
    expect(page.locator("th[aria-sort]")).to_have_count(1)
    expect(page.get_by_role("columnheader", name="Amount")).to_have_attribute("aria-sort", "ascending")

How to check

A browser test tabs to a header button and presses Enter. That header has aria-sort, no other header has it, and the status region names the order.

For agents: if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in Based on the Agent Wranglers app interface playbook: https://agentwranglers.se/playbook/app-ui/lists-and-tables/sortable-headers.