A light-DOM behaviour element wrapped around a <table> the server rendered. It adds a tri-state selection column, sort headers that announce aria-sort, and a sticky header - and never reorders a row.
A table wider than its container scrolls sideways inside the element, not the page. While it overflows, the element is a named region in the tab order - named by the table’s <caption> - so a keyboard can scroll it as well as a thumb. It stops being a tab stop once it fits.
<trdata-row-value="INV001"><td>INV001</td><td>Paid</td><td>Credit Card</td><td>12 October 2026</td><tdstyle="text-align:right">$250.00</td></tr>
<trdata-row-value="INV002"><td>INV002</td><td>Pending</td><td>PayPal</td><td>14 October 2026</td><tdstyle="text-align:right">$150.00</td></tr>
<trdata-row-value="INV003"><td>INV003</td><td>Unpaid</td><td>Bank Transfer</td><td>20 October 2026</td><tdstyle="text-align:right">$350.00</td></tr>
</tbody>
</table>
</hui-table>
API
Attributes5
Attributes
Name
Type
Default
Description
selectable
boolean
false
Adds the selection column and its tri-state header control.
sticky
boolean
false
Pins <thead> while the body scrolls.
sort
string
-
The data-sort-key the server sorted by. Mirrored to aria-sort on that header.
sort-direction
"ascending" | "descending" | "none"
"none"
Which way. The matching header gets aria-sort; the others get none.
selection
comma-separated <code>data-row-value</code>s
""
Which rows are selected. The reader changes it; the server sends it back.
Properties5
Properties
Name
Type
Default
Description
selectable
boolean
false
Reflects to selectable.
sticky
boolean
false
Reflects to sticky.
sort
string
''
Reflects to sort.
sortDirection
SortDirection
'none'
Reflects to sort-direction.
selection
string
''
Reflects to selection.
Events2
Events
Name
Description
hui-sort
A sort header was activated. bubbles, composed and cancelable: preventing it stops the attribute changing, so a host doing a round-trip can wait for the server. Detail: { column, direction }.
hui-selection-change
The selection changed, after selection is updated. Detail: { values }.
Slots1
Slots
Name
Description
(default)
The <table> the server rendered. Rows stay the host’s <tr>.
CSS custom properties2
CSS custom properties
Name
Default
Description
--hui-card
oklch(1 0 0)
Fill behind the sticky header, so rows do not show through.
--hui-border
oklch(0.922 0.005 325.62)
Row hairlines.
Classes3
Classes
Name
Description
.hui-table__sort
The sort control hui-table wires into a header. Its indicator shows the current direction from the header’s aria-sort.
The selection cell hui-table adds when selectable.
.hui-table__hit
The 24px label hui-table wraps each selection box in, so a thumb has the WCAG 2.5.8 minimum to hit while the box is drawn at 16px (ISS-020).
States
Sticky header
sticky keeps the header in place while the rows scroll under it. The header sticks inside the element's own scroll box, so the example gives the element a height to scroll within.
The server owns the rows, the sort column and direction, and which rows are selected. It renders data-sort-key on each sortable <th> and data-row-value on each <tr>. The element is not form-associated and submits nothing; a selection that drives an action is the server’s, sent back as the selection attribute. An hx-swap replaces the table wholesale and the element re-reads the attributes, so a selection the reader made loses to the one the server sent.
Accessibility
The sorted header carries aria-sort, read from the server’s sort/sort-direction, so the indicator is announced.
The header checkbox is indeterminate when some rows are selected and checked only when all are.
Every selection control has a name, so a screen reader announces the row it belongs to.
Sorting is a server round-trip; the element never reorders rows, so no row order changes under a screen-reader user.
A table wider than its container scrolls inside the element. While it overflows, the element is a role="region" with tabindex="0", named by the table’s caption or aria-label, so a keyboard can reach and scroll it (WCAG 2.1.1). A role or tabindex the host wrote is left alone.
Each selection box is drawn at 16px inside a 24px label, the WCAG 2.5.8 minimum for a pointer.
Keyboard
Keyboard
Keys
Action
Tab
Reaches each selection control and sort header in order.
Space
Toggles a selection checkbox.
Enter, Space
Activates a sort header, emitting hui-sort.
Gotchas
The rows are the server’s: the element never sorts, filters or paginates. Activating a header emits hui-sort and nothing moves until the server re-renders.
It is light DOM, so .hui-table and the host’s own styles still reach the table. There is no shadow root and no ::part.