Table

June 7, 2026 · View on GitHub

Data-driven table. Columns schema + row data, with sortable headers, optional selection, striped / bordered variants, and template-per-column cell rendering.

Contract

<kp-table> takes a [columns] schema and [data] array and renders an HTML <table>. Cells render via one of two paths:

  1. Accessor fallbackcolumn.accessor: (row) => value returns the raw value to print. Fast path for simple strings / numbers.
  2. Template<ng-template kpTableCell="id" let-row let-i="index"> renders whatever you want inside the cell. Used for badges, action buttons, custom formatting.

Header labels can likewise be templated via <ng-template kpTableHeader="id"> when the plain label isn't enough (tooltips, custom sort widgets).

Anatomy

Table
├─ Header row
│   ├─ [Select-all checkbox]
│   └─ Header cells  — label + sort indicator (if column.sortable)
└─ Body
    └─ Row  — [checkbox] + cells + optional selected styling

API

<kp-table> inputs

NameTypeDefaultDescription
size'sm' | 'md' | 'lg''md'Row height + padding
columnsKpTableColumn<T>[][]Column schema
dataT[][]Row data
stripedbooleanfalseZebra stripes on alternating rows
borderedbooleanfalse1px outer border + rounded corners
selectablebooleanfalseAdd checkbox column; row clicks toggle selection
selectedT[][]Two-way bindable — currently selected rows
sortKpTableSort | nullnullTwo-way bindable — active sort (you apply it to data)
emptyMessagestring'No data'Shown when data is empty
trackBy(row: T, i: number) => unknownnullOverride row tracking for @for

<kp-table> outputs

NamePayloadFires when
selectedChangeT[]Selection changes
sortChangeKpTableSort | nullUser clicks a sortable header (third click clears)
rowClickTA row is clicked (independent of selection toggle)

KpTableColumn<T>

interface KpTableColumn<T = unknown> {
  id: string;                                  // stable id — referenced by kpTableCell / kpTableHeader
  label: string;                               // header text
  align?: 'left' | 'center' | 'right';          // cell + header alignment
  sortable?: boolean;                          // show sort indicator, fire sortChange on click
  width?: string;                              // CSS length for the th's width
  accessor?: (row: T) => unknown;              // fallback when no cell template exists
}

interface KpTableSort {
  columnId: string;
  direction: 'asc' | 'desc';
}

Templates

<kp-table [columns]="cols" [data]="rows">
  <ng-template kpTableCell="status" let-row>
    <kp-badge [color]="row.active ? 'success' : 'neutral'">
      {{ row.active ? 'Active' : 'Inactive' }}
    </kp-badge>
  </ng-template>

  <ng-template kpTableHeader="status">
    Status <span aria-label="Help">?</span>
  </ng-template>
</kp-table>

Sorting

The component emits sortChange but does NOT sort data for you — hand the new sort spec to your data layer (API query, in-memory sort, RxJS pipe) and hand back a sorted array via [data].

The third click on an already-sorted column clears sort (emits null).

Selection

  • selectable="true" inserts a checkbox column.
  • Row clicks toggle selection (ignore if you stop propagation in a custom cell template).
  • Header "select all" toggles between all rows and none.

Selection is index-blind — it compares rows by reference. Provide trackBy if the same row identity can shift between renders (e.g., after refetch).

Accessibility

  • Semantic <table> / <thead> / <tbody> / <tr> / <th scope="col"> / <td>.
  • Sortable headers carry aria-sort="ascending" | "descending" | "none".
  • Checkboxes carry row-specific aria-label.
  • Tab / space interacts with checkboxes and sortable header buttons.

Do / Don't

Do

  • Use accessor for plain text/number columns — it's lighter than a template.
  • Template complex cells (badges, avatars, action menus) rather than returning HTML from accessor.
  • Externalize sorting/paging — <kp-table> renders state; your data layer owns it.

Don't

  • Don't embed a full action menu inside the row click zone — wrap actions in a cell template with (click)="$event.stopPropagation()".
  • Don't rely on selected identity matching after a data refetch — provide a stable trackBy.

References

Changelog

  • 0.1.0 — Initial release. Columns schema, template-per-column rendering, sortable headers, selection with tri-state select-all, striped / bordered variants, 3 sizes.