QdsColumnHeader

A single sortable table header cell with built-in sort, alignment and resize support.

Overview

The qds-column-header component encapsulates sorting, alignment and column resize behaviour for a single table header cell. Built as its own component so these behaviours are specified and tested once, rather than reimplemented inside qds-data-table and again in every bespoke grid in Q360.

The <th> composition rule

A <th> must be a direct child of <tr> for the table layout algorithm to work. A custom element cannot be a <th>, and wrapping one breaks column sizing.

Resolution: qds-data-table creates real <th> elements and puts a qds-column-header inside each one. The header component sets display: block; width: 100% on its host so it fills the cell. The <th> carries the width and alignment; the header component carries the interactive content.

This is the only arrangement that keeps native table layout and gives the header its own encapsulated behaviour. The obvious alternative — display: table-cell on a custom element — silently breaks colspan, sticky headers and column sizing.

The aria-sort ownership split

The <th> owns aria-sort, not this component. aria-sort must be on the element with role="columnheader", which is the native <th>. This component never sets aria-sort on its own host; assistive tech will not find it there.

qds-data-table writes aria-sort="ascending", "descending" or "none" onto the <th> based on the sort state it manages.

Usage

<th aria-sort="descending">
  <qds-column-header
    key="marketValue"
    label="Market value"
    sortable
    sort-direction="desc"
    align="end"
  ></qds-column-header>
</th>

When the label is an icon or abbreviation, provide an accessible name via aria-label-text:

<th>
  <qds-column-header
    key="delta"
    label="Δ"
    aria-label-text="Daily change"
    sortable
    align="end"
  ></qds-column-header>
</th>

For multi-column sort, set sort-priority to render a priority number:

<th aria-sort="ascending">
  <qds-column-header
    key="tradeDate"
    label="Trade date"
    sortable
    sort-direction="asc"
    sort-priority="1"
  ></qds-column-header>
</th>

API Reference

Attributes

Properties

None beyond reflected attributes.

CSS Parts

CSS Custom Properties

Events

The header does not apply the resulting width itself. qds-data-table owns column widths; a header that resized itself would fight the table’s layout.

Sort cycle

Three states: unsorted -> ascending -> descending -> unsorted. Returning to unsorted matters in financial tables, where the original order is often a meaningful sequence (trade order, ledger order) that the user needs to get back to without reloading.

Shift-click adds to a multi-column sort rather than replacing it. The header does not track multi-sort state itself; it fires the event with additive: true and the table decides.

Alignment

align="end" puts the sort indicator to the left of the label in DOM order, so the label stays flush with right-aligned numerals below it. This detail is what makes a numeric column look right.

Keyboard

The host is tabindex="0" when sortable, absent otherwise. The resize handle is a separate tab stop with its own aria-label (e.g. “Resize Market value column”).

Example

<table>
  <thead>
    <tr>
      <th>
        <qds-column-header key="name" label="Name" sortable></qds-column-header>
      </th>
      <th aria-sort="descending">
        <qds-column-header
          key="marketValue"
          label="Market value"
          sortable
          sort-direction="desc"
          align="end"
          resizable
        ></qds-column-header>
      </th>
    </tr>
  </thead>
  <tbody>
    <tr><td>Alpha Fund</td><td>1,234,567</td></tr>
  </tbody>
</table>

<script>
  document.querySelector('qds-column-header').addEventListener('qds-sort', (e) => {
    console.log('Sort requested:', e.detail.key, e.detail.direction, 'additive:', e.detail.additive);
  });
</script>