Data display

Data table

@xui/data-table

A virtualized data grid — only the visible rows are in the DOM, so it stays smooth at 100k+ rows. Columns resize (drag the header divider), auto-fit (double-click the divider), reorder (drag the header) and sort (click the header). Cells select as regions (click, shift-click for a range, ⌘/Ctrl-click for disjoint blocks, ⌘/Ctrl+A for all), the focused cell moves with the arrow keys (hold Shift to extend), and ⌘/Ctrl+C copies the selection as TSV. Built on the pure @xui/core/grid region/size/locator model.

Install

pnpm add @xui/data-table

Add the barrel to a standalone component's imports:

import { XuiDataTableImports } from '@xui/data-table';

@Component({
  imports: [XuiDataTableImports],
  // …
})

Examples

Default

Loading preview…

Hundred thousand rows

Loading preview…

Selection and copy

Loading preview…

Custom cells

Source only — this one needs a component of its own to run, so there is nothing to render here.

<xui-data-table class="w-[760px]" [data]="rows" [columns]="columns" [height]="360">
  <ng-template xuiDataCell let-value let-column="column">
    @if (column.id === 'age') {
      <span [class]="value >= 50 ? 'text-warning font-medium' : ''">{{ value }}</span>
    } @else {
      <span class="truncate">{{ value }}</span>
    }
  </ng-template>
</xui-data-table>

Frozen columns

Loading preview…

Row headers

Loading preview…

Cell formatters

Source only — this one needs a component of its own to run, so there is nothing to render here.

<xui-data-table class="w-[760px]" [data]="rows" [columns]="columns" [height]="360" [rowHeight]="72">
  <ng-template xuiDataCell let-value let-column="column">
    @switch (column.id) {
      @case ('body') { <xui-truncated-format [value]="value" [length]="70" /> }
      @case ('meta') { <xui-json-format [value]="value" [pretty]="true" [length]="120" /> }
      @default { <span class="truncate">{{ value }}</span> }
    }
  </ng-template>
</xui-data-table>

Editable cells

Source only — this one needs a component of its own to run, so there is nothing to render here.

<xui-data-table class="w-[600px]" [data]="rows" [columns]="columns" [height]="360" [selectable]="false">
  <ng-template xuiDataCell let-value let-row="row" let-column="column">
    @if (column.id === 'id') {
      <span class="tabular-nums">{{ value }}</span>
    } @else {
      <xui-editable-cell [value]="value" (edited)="save(row, column, $event)" />
    }
  </ng-template>
</xui-data-table>

API

XuiDataCell

directiveng-template[xuiDataCell]

Marks an <ng-template> as the cell renderer for a {@link XuiDataTable}.

XuiDataTable

componentxui-data-table

A virtualized data grid: only the visible rows are in the DOM, so it stays smooth at 100k+ rows. Columns are resizable (drag the header divider), reorderable (drag the header), auto-fit (double-click the divider) and sortable; cells support region selection (shift/ctrl-click, arrow keys) and copy-to-clipboard. Cells render with an optional [xuiDataCell] template. Built on the pure @xui/core/grid region/size/locator model.

Inputs

NameTypeDefaultclassClassValueExtra classes, merged into the component's own rather than replacing them.''datareadonly T[]The rows. Rendered through a virtual viewport, so a large array costs no more DOM than a small one.[]columnsreadonly XuiDataColumn<T>[]The column definitions, in their initial order. Reordering or resizing by the user is held separately, and resets when this changes.[]rowHeightnumberHeight of a body row in pixels. Fixed rather than measured — it is what the virtual scroller maps a scroll offset onto a row with.36headerHeightnumberHeight of the header row in pixels. Subtracted from height to give the scrolling viewport.36defaultColumnWidthnumberWidth in pixels for a column that declares none of its own.150minColumnWidthnumberFloor for a column's width in pixels, both while dragging a resize handle and when auto-fitting to content.48heightnumberViewport height in pixels.400overscannumberExtra rows and columns rendered beyond the viewport, so a fast scroll does not expose blank space. Higher values trade DOM for smoothness.4selectablebooleanAllow selecting cell regions (shift-range, ctrl/cmd-toggle, arrow keys).truemultiplebooleanAllow more than one disjoint region (ctrl/cmd-click, select-all).truereorderablebooleanAllow reordering columns by dragging their headers.truerowHeaderbooleanShow a leading row-header gutter (row numbers; click to select the whole row).falserowHeaderWidthnumberWidth of the row-header gutter in pixels.48frozenColumnsnumberNumber of leading columns to freeze (pinned left while the rest scroll).0frozenRowsnumberNumber of leading rows to freeze (pinned below the header while the rest scroll).0focusedCelltwo-wayCellCoord | nullThe focused cell. Two-way bindable with [(focusedCell)].nullselectiontwo-wayXRegion[]The selected regions. Two-way bindable with [(selection)].[]

Outputs

NameTypecellClicked{ row: T; rowIndex: number; column: XuiDataColumn<T> }Emits the clicked cell, with its row, the row's index in data, and the column.sortChangeXuiSortState | nullEmits the new sort, or null when sorting is cleared. Handle it for server-side sorting; local sorting is applied regardless.copiedstringEmitted with the tab-separated text when the selection is copied.columnReorder{ from: number; to: number }Emitted when a column is dragged to a new position (indices into the displayed order).

Methods

buildClipboardText(): stringThe selected region(s) as tab-separated rows (unselected cells within the bounding box are blank).reorderColumn(from: number, to: number): voidMove a column from one displayed position to another, carrying its width.scrollToRow(rowIndex: number): voidMove the scroll position so a given row is at the top (used by tests/consumers).

XuiEditableCell

componentxui-editable-cell

An inline-editable cell value. Double-click (or press Enter while focused) to edit; commit with Enter or blur, cancel with Escape. value is two-way bindable and edited fires with the committed text only when it changed.

Inputs

NameTypeDefaultclassClassValueExtra classes, merged into the component's own rather than replacing them.''valuetwo-waystringThe cell value. Two-way bindable with [(value)].''editablebooleanWhether editing is allowed.trueplaceholderstringPlaceholder shown in the editor when the value is empty.''

Outputs

NameTypeeditedstringEmitted with the committed value when it changed.cancelledvoidEmitted when editing is cancelled (Escape).

Methods

edit(): voidEnter edit mode (no-op when not editable).commit(): voidCommit the draft, emitting edited if it changed.cancel(): voidDiscard the draft and leave edit mode.

XuiJsonFormat

componentxui-json-format

Renders a cell value as JSON in a monospace <code>, truncated past length characters (via {@link XuiTruncatedFormat}). omitQuotes prints a plain string value without the surrounding quotes; pretty indents the output.

Inputs

NameTypeDefaultclassClassValueExtra classes, merged into the component's own rather than replacing them.''valueunknownThe value to serialize as JSON.nullomitQuotesbooleanPrint a plain-string value without surrounding quotes.falseprettybooleanIndent the JSON with two spaces.falselengthnumberMaximum characters before truncating.200showExpandbooleanWhether the truncated value shows an inline "more"/"less" toggle.true

XuiTruncatedFormat

componentxui-truncated-format

Truncates a long cell value to length characters, appending an ellipsis and a title tooltip with the full text. When showExpand is set (the default), a "more"/"less" toggle reveals or re-collapses the full value inline.

Inputs

NameTypeDefaultclassClassValueExtra classes, merged into the component's own rather than replacing them.''valueunknownThe value to render (coerced to a string).''lengthnumberMaximum characters before truncating.80showExpandbooleanWhether to show an inline "more"/"less" toggle when truncated.true

Source

libs/ui/data-table/xui

xUI 2.0.0 — Apache 2.0 licensed. Built with Angular and Tailwind CSS.