Table composes several directives and components sharing one DI context.

table.ts
import {
  KuiCellDirective,
  KuiRowDirective,
  KuiSelectCellComponent,
  KuiSelectThComponent,
  KuiTableDirective,
  KuiThDirective,
  KuiThGroupDirective,
} from '@kikita-labs/ui';

Apply kuiTable to a real table element, keep native thead/tbody/tr/th/td markup, and read #table='kuiTable' for sorted rows.

Ava ChenEngineerActive
Liam OseiDesignerInvited
Noor MalikProduct ManagerActive
Priya RaoSupportSuspended
Tomas SilvaEngineerActive
<table kuiTable [data]="rows" #table="kuiTable">
  <thead>
    <tr kuiThGroup>
      <th kuiTh sortKey="name">Name</th>
      <th kuiTh sortKey="role">Role</th>
      <th kuiTh sortKey="status">Status</th>
    </tr>
  </thead>
  <tbody>
    @for (row of table.sortedData(); track row.id) {
      <tr kuiRow [value]="row">
        <td kuiCell>{{ row.name }}</td>
        <td kuiCell>{{ row.role }}</td>
        <td kuiCell>{{ row.status }}</td>
      </tr>
    }
  </tbody>
</table>

sortKey on th[kuiTh] enables cycling through asc, desc, and clear via a native button. Uncontrolled sort (no (sortChange) listener) sorts data locally and exposes results as sortedData().

  • Controlled sort: observe (sortChange) and reorder data yourself.
  • Uncontrolled sort: leave (sortChange) unbound and read table.sortedData().
  • comparator overrides the default comparison for a column.

The selection column only renders when (selectionChange) is observed. th[kuiSelectTh] renders the select-all checkbox; td[kuiSelectCell] renders each row's checkbox.

Ava ChenEngineerActive
Liam OseiDesignerInvited
Noor MalikProduct ManagerActive
Priya RaoSupportSuspended
Tomas SilvaEngineerActive

0 of 5 selected

<table kuiTable [data]="rows" (selectionChange)="onSelectionChange($event)">
  <thead>
    <tr kuiThGroup>
      <th kuiSelectTh ariaLabel="Select all team members"></th>
      <th kuiTh sortKey="name">Name</th>
      <th kuiTh sortKey="role">Role</th>
      <th kuiTh sortKey="status">Status</th>
    </tr>
  </thead>
  <tbody>
    @for (row of rows; track row.id) {
      <tr kuiRow [value]="row">
        <td kuiSelectCell [ariaLabel]="'Select ' + row.name"></td>
        <td kuiCell>{{ row.name }}</td>
        <td kuiCell>{{ row.role }}</td>
        <td kuiCell>{{ row.status }}</td>
      </tr>
    }
  </tbody>
</table>
<p class="row-selection-table-example__note">
  {{ selected().length }} of {{ rows.length }} selected
</p>

sticky on th[kuiTh] pins that header cell horizontally; sticky on tr[kuiThGroup] pins the whole header row vertically inside a scrolling container.

Ava ChenEngineerPlatformRemoteActive
Liam OseiDesignerProductBerlinInvited
Noor MalikProduct ManagerProductRemoteActive
Priya RaoSupportOperationsSingaporeSuspended
Tomas SilvaEngineerPlatformLisbonActive
<div class="sticky-header-table-example__scroll">
  <table kuiTable [data]="rows" #table="kuiTable">
    <thead>
      <tr kuiThGroup sticky>
        <th kuiTh sortKey="name" sticky>Name</th>
        <th kuiTh sortKey="role">Role</th>
        <th kuiTh sortKey="department">Department</th>
        <th kuiTh sortKey="location">Location</th>
        <th kuiTh sortKey="status">Status</th>
      </tr>
    </thead>
    <tbody>
      @for (row of table.sortedData(); track row.id) {
        <tr kuiRow [value]="row">
          <td kuiCell>{{ row.name }}</td>
          <td kuiCell>{{ row.role }}</td>
          <td kuiCell>{{ row.department }}</td>
          <td kuiCell>{{ row.location }}</td>
          <td kuiCell>{{ row.status }}</td>
        </tr>
      }
    </tbody>
  </table>
</div>

All three behaviors compose on the same table through the shared kuiTable DI context.

Ava ChenEngineerPlatformActive
Liam OseiDesignerProductInvited
Noor MalikProduct ManagerProductActive
Priya RaoSupportOperationsSuspended
Tomas SilvaEngineerPlatformActive

0 of 5 selected

<div class="combined-table-example__scroll">
  <table kuiTable [data]="rows" #table="kuiTable" (selectionChange)="onSelectionChange($event)">
    <thead>
      <tr kuiThGroup sticky>
        <th kuiSelectTh ariaLabel="Select all team members"></th>
        <th kuiTh sortKey="name" sticky>Name</th>
        <th kuiTh sortKey="role">Role</th>
        <th kuiTh sortKey="department">Department</th>
        <th kuiTh sortKey="status">Status</th>
      </tr>
    </thead>
    <tbody>
      @for (row of table.sortedData(); track row.id) {
        <tr kuiRow [value]="row">
          <td kuiSelectCell [ariaLabel]="'Select ' + row.name"></td>
          <td kuiCell>{{ row.name }}</td>
          <td kuiCell>{{ row.role }}</td>
          <td kuiCell>{{ row.department }}</td>
          <td kuiCell>{{ row.status }}</td>
        </tr>
      }
    </tbody>
  </table>
</div>
<p class="combined-table-example__note">{{ selected().length }} of {{ rows.length }} selected</p>

Inputs and outputs verified against @kikita-labs/ui v1.6.1 public typings.

NameTypeDefaultDescription
dataT[][]Source rows for sorting and selection. Selector: table[kuiTable].
size'xs' | 'sm' | 'md' | 'lg''md'Table density and typography scale. Selector: table[kuiTable].
selectionChangeoutput<T[]>Emits current selection whenever it changes. Observing this output is what makes the selection column appear. Selector: table[kuiTable].
sortChangeoutput<KuiActiveSortState | null>Emits the active sort key/direction, or null when sort is cleared. When observed, kuiTable hands row ordering to the parent instead of sorting locally. Selector: table[kuiTable].
sortStateSignal<KuiSortState>Current sort state (null when no column is sorted). Selector: table[kuiTable].
sortedDataSignal<T[]>Rows in current sort order. Use this in the template instead of data when sort is uncontrolled. Selector: table[kuiTable].
isSelected(item)(item: T) => booleanReturns whether a given row value is currently selected. Selector: table[kuiTable].
#table="kuiTable"exportAsTemplate reference for reading sortedData(), sortState(), and selection state.
sortKeystring | undefined-Enables sort for this header cell; usually matches a row property name. Selector: th[kuiTh].
comparator(a: T, b: T) => number-Custom sort function for the column, used instead of default comparison. Selector: th[kuiTh].
stickybooleanfalsePins this header cell horizontally with position: sticky. Selector: th[kuiTh]. Also available on tr[kuiThGroup] to pin the whole header row vertically.
valueTThe data object represented by this row. Selector: tr[kuiRow], required.
ariaLabelstring'Select all rows'Accessible label for the select-all checkbox. Selector: th[kuiSelectTh].
ariaLabelstring-Accessible label for a row selection checkbox, e.g. "Select " + row.name. Selector: td[kuiSelectCell].
td[kuiCell]marker directiveApplies Kikita table cell styling to a native td. No inputs in the installed version.
--kui-table-font-sizeCSS custom propertyBase font size for table text.
--kui-table-th-pyCSS custom propertyVertical padding for header cells.
--kui-table-cell-pxCSS custom propertyHorizontal padding for cells.
--kui-table-row-pyCSS custom propertyVertical padding for body row cells.
--kui-table-th-fgCSS custom propertyHeader cell text color.
--kui-table-th-bgCSS custom propertyHeader cell background color.
--kui-table-borderCSS custom propertyOuter table border color.
--kui-table-row-borderCSS custom propertyRow divider border color.
--kui-table-row-hover-bgCSS custom propertyRow background on hover.
--kui-table-row-selected-bgCSS custom propertyRow background when selected.
--kui-table-row-selected-accentCSS custom propertyAccent color for selected rows.
--kui-table-sort-active-colorCSS custom propertyColor used for the active sort indicator.
--kui-table-bgCSS custom propertyTable background color.

Table keeps real table semantics and native interactive elements for sort and selection.

  • Keep real table, thead, tbody, tr, th, and td markup; kuiTable does not replace table semantics.
  • Sortable headers keep aria-sort on the th while a native button owns keyboard and click interaction.
  • Selection uses native checkboxes; pass a human-readable ariaLabel on kuiSelectTh and kuiSelectCell.
  • Add a native caption when the surrounding page does not already provide a clear table title.
  • Avoid placing complex interactive widgets inside cells unless their focus order and labels are explicitly tested.

One documented library behavior is not available in the installed package version.

  • The library source documents sticky on td[kuiCell] for pinning body columns, but in the installed @kikita-labs/ui version (see the API section above for the exact version) KuiCellDirective exposes no public inputs, so body cells cannot be pinned through a directive input yet. Only header-side stickiness (th[kuiTh] sticky and tr[kuiThGroup] sticky) is available in this version. This page documents only the working header behavior; do not add sticky to td[kuiCell] until the installed package exposes it.