HyTable
Usage examples on this page are written for Lit / plain HTML (
<hy-table>). The same component ships asHyTablein@whitespaceux/harmony-react(native React 19), with typed wrappers for Angular, Solid, Svelte, and Vue. The API reference below applies to all frameworks.
A semantic, lightweight data table for standard row/column display.
Rows are passed as rows: T[] against a columns: TableColumn<T>[] definition. The component is stateless with respect to data — sorting is controlled by the consumer via the sortKey + sortDirection props; the component only emits a sort event and updates the visual indicator / aria-sort when the consumer echoes the state back.
Row density is inherited from the surrounding context — wrap a region in data-density="compact" (or comfort) to shift cell padding without passing a prop per table. There is no local size override; tables are content-dense by design.
When a consumer outgrows hy-table (virtualization, selection, column resizing, multi-sort, pinning, grouping), they swap to hy-datagrid.
Examples
Sortable table — columns and rows are JS properties
<hy-table label="Team members"></hy-table>
<script>
const table = document.querySelector('hy-table');
table.columns = [
{ key: 'name', label: 'Name', sortable: true },
{ key: 'role', label: 'Role' },
{ key: 'commits', label: 'Commits', sortable: true, align: 'end' },
];
table.rows = [
{ name: 'Alice', role: 'Admin', commits: 128 },
{ name: 'Bob', role: 'Editor', commits: 74 },
{ name: 'Carol', role: 'Viewer', commits: 31 },
];
// Sorting is controlled: sort the data yourself, then echo the state back.
table.addEventListener('sort', (e) => {
const { key, direction } = e.detail;
table.sortKey = key;
table.sortDirection = direction;
table.rows = [...table.rows].sort((a, b) => {
if (direction === null) return 0;
const order = direction === 'asc' ? 1 : -1;
return a[key] < b[key] ? -order : a[key] > b[key] ? order : 0;
});
});
</script>Presentation axes — emphasis, dividers, stripes, sticky header
<hy-table label="Inventory" emphasis="outlined" dividers="cells" striped sticky-header></hy-table>Loading and empty states
<hy-table label="Users" loading></hy-table>
<hy-table label="Users" empty-text="No users yet"></hy-table>API
Properties
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
rows | — | T[] | [] | Row data. Property-only — not an attribute (arrays can't be reflected). |
columns | — | TableColumn<T>[] | [] | Column definitions. Property-only. |
emphasis | emphasis | 'solid' | 'outlined' | 'tinted' | 'plain' | 'solid' | Visual chrome weight on the table surface. |
dividers | dividers | 'none' | 'rows' | 'cells' | 'rows' | Cell-divider treatment. Orthogonal to emphasis. |
striped | striped | boolean | false | Zebra-stripe rows. |
hoverable | hoverable | boolean | true | Highlight rows on hover. |
stickyHeader | sticky-header | boolean | false | position: sticky; top: 0 on the header row. The header sticks against the table's OWN scroll-container, so the table must be the vertical scroller: place it in a height-constrained container (a flex item with min-height: 0, or an explicit height) and it caps to that height and scrolls internally. A wrapper with overflow: auto around an unbounded table does NOT work — the table grows to its content and the wrapper scrolls instead, leaving the header with nothing to stick within. |
stickyFirstColumn | sticky-first-column | boolean | false | position: sticky; left: 0 on the first cell of each row. |
caption | caption | string | '' | Renders a <caption>. When label is set separately the caption is visually hidden. |
label | label | string | '' | aria-label on the table — required if caption is empty. |
loading | loading | boolean | false | Render skeleton rows instead of data. |
sortKey | sort-key | string | null | null | Controlled sort column — component renders the indicator at sortKey. |
sortDirection | sort-direction | 'asc' | 'desc' | null | Controlled sort direction. |
emptyText | empty-text | string | 'No data' | Fallback when rows.length === 0 and no empty slot is provided. |
Events
| Event | Detail | Description |
|---|---|---|
sort | — | TableSortDetail — fired when a sortable header is activated. |
Slots
| Slot | Description |
|---|---|
empty | Custom empty-state content (overrides emptyText). |
loading | Custom loading placeholder (overrides the default skeletons). |
header-{key} | Override a specific column's header content. Replace {key} with the column key (e.g. header-email). |
cell-{key} | Override a specific column's cell content (static). Replace {key} with the column key (e.g. cell-status). |
CSS Parts
| Part | Description |
|---|---|
base | Outer scroll-container wrapper. |
table | The <table> element. |
head | <thead>. |
body | <tbody>. |
row | Each <tr> (both head and body). |
header-cell | <th>. |
cell | <td>. |
sort-button | <button> inside a sortable <th>. |
sort-indicator | Arrow icon beside the sort button. |
caption | <caption>. |
empty | Container for the empty-state slot/fallback. |
CSS Custom Properties
| Property | Description |
|---|---|
--hy-table-border-color | Horizontal/vertical border colour. |
--hy-table-header-background | Header row background. |
--hy-table-header-color | Header row text colour. |
--hy-table-row-hover-background | Row background on hover. |
--hy-table-row-stripe-background | Alternate row background when striped. |
--hy-table-cell-padding-x | Horizontal cell padding. |
--hy-table-cell-padding-y | Vertical cell padding. |
--hy-table-radius | Outer container border-radius. |
--hy-table-stroke | Border width. |
--hy-table-hover-duration | Transition duration for row hover state feedback |
--hy-table-hover-easing | Transition easing for row hover state feedback |