Base UI (Angular) data-table (Pro) is an ops table: sort, page, select, resize, optional virtualize, serverSide mode, and cell templates via ng-template baseTableCell. Copy it in, pass columns and data, and handle pageChange when the server owns the slice. Pair with httpResource for server paging. Cookbooks: invoice-table, http-resource.
Install with npx base-ui-cli add data-table.
Unlock it plus 90 pro blocks, widgets & layouts — $99 one-time. Lifetime updates, 14-day money-back guarantee.
ID | Name | Email | Role | Status | Joined |
|---|---|---|---|---|---|
| 1 | Alice Johnson | alice@example.com | Admin | Active | 2024-01-15 |
| 2 | Bob Smith | bob@example.com | Editor | Active | 2024-02-20 |
| 3 | Carol White | carol@example.com | Viewer | Inactive | 2024-03-10 |
| 4 | David Brown | david@example.com | Editor | Active | 2024-04-05 |
| 5 | Eve Davis | eve@example.com | Admin | Active | 2024-05-12 |
Playground
<base-data-table [columns]="columns" [data]="users" sortable [striped]="true" [bordered]="true" [pageable]="true" [hoverable]="true" [selectable]="false" [resizable]="false"> </base-data-table>
Define [columns] as an array of TableColumn objects and pass [data] as an array of objects.
ID | Name | Email | Role | Status | Joined |
|---|---|---|---|---|---|
| 1 | Alice Johnson | alice@example.com | Admin | Active | 2024-01-15 |
| 2 | Bob Smith | bob@example.com | Editor | Active | 2024-02-20 |
| 3 | Carol White | carol@example.com | Viewer | Inactive | 2024-03-10 |
| 4 | David Brown | david@example.com | Editor | Active | 2024-04-05 |
| 5 | Eve Davis | eve@example.com | Admin | Active | 2024-05-12 |
<base-data-table [columns]="columns" [data]="users" [sortable]="true" [pageable]="true" [pageSize]="10" [striped]="true" [bordered]="true" (rowClick)="onRowClick($event)"> </base-data-table>
TypeScript
import { Component } from '@angular/core';
// Install: npx base-ui-cli add data-table
// Paths are relative to aliases.components (default: src/app/components)
import { DataTableComponent } from './data-table/data-table.component';
import { TableCellContext, TableCellDirective } from './data-table/table-cell.directive';
import { TableEmptyDirective } from './data-table/table-empty.directive';
@Component({
selector: 'app-data-table-example',
imports: [
DataTableComponent,
TableCellDirective,
TableEmptyDirective
],
template: `...`
})
export class DataTableExampleComponent {}Install
npx base-ui-cli add data-table
Columns support key, label, sortable, width, and align ('left' | 'center' | 'right'). Cells default to row[column.key]. Project ng-template baseTableCell="key" to render a badge, a button, or any other widget without forking the table. Import TableCellDirective next to DataTableComponent. Interactive cells should call $event.stopPropagation() so they do not also fire rowClick.
Match baseTableCell to a column key. Context: let-row, let-value="value", let-column="column", let-index="index". Columns without a template still interpolate the raw value.
Name | Email | Status | |
|---|---|---|---|
| Alice Johnson | alice@example.com | ||
| Bob Smith | bob@example.com | ||
| Carol White | carol@example.com | ||
| David Brown | david@example.com | ||
| Eve Davis | eve@example.com |
<base-data-table [columns]="columns" [data]="users" bordered>
<ng-template baseTableCell="status" let-value="value">
<base-badge [color]="statusColor(value)" size="sm" shape="rounded">Paid</base-badge>
</ng-template>
<ng-template baseTableCell="actions" let-row>
<button type="button" (click)="$event.stopPropagation(); edit(row)">Edit</button>
</ng-template>
</base-data-table>TypeScript
import { Component } from '@angular/core';
// Install: npx base-ui-cli add data-table
// Paths are relative to aliases.components (default: src/app/components)
import { DataTableComponent } from './data-table/data-table.component';
import { TableCellContext, TableCellDirective } from './data-table/table-cell.directive';
import { TableEmptyDirective } from './data-table/table-empty.directive';
@Component({
selector: 'app-data-table-example',
imports: [
DataTableComponent,
TableCellDirective,
TableEmptyDirective
],
template: `...`
})
export class DataTableExampleComponent {}Install
npx base-ui-cli add data-table
Set [loading]="true" to show skeleton rows while data loads. The data table will render animated placeholder rows matching your column count.
ID | Name | Email | Role | Status | Joined |
|---|---|---|---|---|---|
| 1 | Alice Johnson | alice@example.com | Admin | Active | 2024-01-15 |
| 2 | Bob Smith | bob@example.com | Editor | Active | 2024-02-20 |
| 3 | Carol White | carol@example.com | Viewer | Inactive | 2024-03-10 |
| 4 | David Brown | david@example.com | Editor | Active | 2024-04-05 |
| 5 | Eve Davis | eve@example.com | Admin | Active | 2024-05-12 |
| 6 | Frank Miller | frank@example.com | Viewer | Active | 2024-06-18 |
| 7 | Grace Wilson | grace@example.com | Editor | Inactive | 2024-07-22 |
| 8 | Hank Taylor | hank@example.com | Viewer | Active | 2024-08-30 |
| 9 | Ivy Anderson | ivy@example.com | Admin | Active | 2024-09-14 |
| 10 | Jack Thomas | jack@example.com | Editor | Inactive | 2024-10-01 |
| 11 | Karen Martinez | karen@example.com | Viewer | Active | 2024-11-05 |
| 12 | Leo Garcia | leo@example.com | Admin | Active | 2024-12-10 |
<base-data-table [columns]="columns" [data]="users" [loading]="loading"> </base-data-table>
TypeScript
import { Component } from '@angular/core';
// Install: npx base-ui-cli add data-table
// Paths are relative to aliases.components (default: src/app/components)
import { DataTableComponent } from './data-table/data-table.component';
import { TableCellContext, TableCellDirective } from './data-table/table-cell.directive';
import { TableEmptyDirective } from './data-table/table-empty.directive';
@Component({
selector: 'app-data-table-example',
imports: [
DataTableComponent,
TableCellDirective,
TableEmptyDirective
],
template: `...`
})
export class DataTableExampleComponent {}Install
npx base-ui-cli add data-table
Striped rows + Bordered
ID | Name | Email | Role | Status | Joined |
|---|---|---|---|---|---|
| 1 | Alice Johnson | alice@example.com | Admin | Active | 2024-01-15 |
| 2 | Bob Smith | bob@example.com | Editor | Active | 2024-02-20 |
| 3 | Carol White | carol@example.com | Viewer | Inactive | 2024-03-10 |
| 4 | David Brown | david@example.com | Editor | Active | 2024-04-05 |
| 5 | Eve Davis | eve@example.com | Admin | Active | 2024-05-12 |
No stripe, no border
ID | Name | Email | Role | Status | Joined |
|---|---|---|---|---|---|
| 1 | Alice Johnson | alice@example.com | Admin | Active | 2024-01-15 |
| 2 | Bob Smith | bob@example.com | Editor | Active | 2024-02-20 |
| 3 | Carol White | carol@example.com | Viewer | Inactive | 2024-03-10 |
| 4 | David Brown | david@example.com | Editor | Active | 2024-04-05 |
| 5 | Eve Davis | eve@example.com | Admin | Active | 2024-05-12 |
<base-data-table [columns]="columns" [data]="users" [striped]="true" [bordered]="true"> </base-data-table>
TypeScript
import { Component } from '@angular/core';
// Install: npx base-ui-cli add data-table
// Paths are relative to aliases.components (default: src/app/components)
import { DataTableComponent } from './data-table/data-table.component';
import { TableCellContext, TableCellDirective } from './data-table/table-cell.directive';
import { TableEmptyDirective } from './data-table/table-empty.directive';
@Component({
selector: 'app-data-table-example',
imports: [
DataTableComponent,
TableCellDirective,
TableEmptyDirective
],
template: `...`
})
export class DataTableExampleComponent {}Install
npx base-ui-cli add data-table
When data is empty, a friendly message is shown. Customize it with emptyMessage, or project ng-template baseTableEmpty for a richer empty row. App-wide defaults come from provideBaseUiI18n().
ID | Name | Email | Role | Status | Joined |
|---|---|---|---|---|---|
No users found matching your filters. | |||||
<base-data-table [columns]="columns" [data]="[]">
<ng-template baseTableEmpty>
<p>No results found.</p>
</ng-template>
</base-data-table>
TypeScript
import { Component } from '@angular/core';
// Install: npx base-ui-cli add data-table
// Paths are relative to aliases.components (default: src/app/components)
import { DataTableComponent } from './data-table/data-table.component';
import { TableCellContext, TableCellDirective } from './data-table/table-cell.directive';
import { TableEmptyDirective } from './data-table/table-empty.directive';
@Component({
selector: 'app-data-table-example',
imports: [
DataTableComponent,
TableCellDirective,
TableEmptyDirective
],
template: `...`
})
export class DataTableExampleComponent {}Install
npx base-ui-cli add data-table
Keep the table shell and put the failure in an alert above it. Reuse emptyMessage when the request returned no rows after an error retry.
ID | Name | Email | Role | Status | Joined |
|---|---|---|---|---|---|
| No users loaded. | |||||
TypeScript
import { Component } from '@angular/core';
// Install: npx base-ui-cli add data-table
// Paths are relative to aliases.components (default: src/app/components)
import { DataTableComponent } from './data-table/data-table.component';
import { TableCellContext, TableCellDirective } from './data-table/table-cell.directive';
import { TableEmptyDirective } from './data-table/table-empty.directive';
@Component({
selector: 'app-data-table-example',
imports: [
DataTableComponent,
TableCellDirective,
TableEmptyDirective
],
template: `...`
})
export class DataTableExampleComponent {}Install
npx base-ui-cli add data-table
Set selectable and bind [(selected)] to an array of rowKey values (defaults to id). Header checkbox selects the current page. selectionChange emits the selected row objects.
Selected ids: none
ID | Name | Email | Role | Status | Joined | |
|---|---|---|---|---|---|---|
| 1 | Alice Johnson | alice@example.com | Admin | Active | 2024-01-15 | |
| 2 | Bob Smith | bob@example.com | Editor | Active | 2024-02-20 | |
| 3 | Carol White | carol@example.com | Viewer | Inactive | 2024-03-10 | |
| 4 | David Brown | david@example.com | Editor | Active | 2024-04-05 | |
| 5 | Eve Davis | eve@example.com | Admin | Active | 2024-05-12 |
<base-data-table [columns]="columns" [data]="users" selectable [(selected)]="selectedIds" (selectionChange)="onSelect($event)"> </base-data-table>
TypeScript
import { Component } from '@angular/core';
// Install: npx base-ui-cli add data-table
// Paths are relative to aliases.components (default: src/app/components)
import { DataTableComponent } from './data-table/data-table.component';
import { TableCellContext, TableCellDirective } from './data-table/table-cell.directive';
import { TableEmptyDirective } from './data-table/table-empty.directive';
@Component({
selector: 'app-data-table-example',
imports: [
DataTableComponent,
TableCellDirective,
TableEmptyDirective
],
template: `...`
})
export class DataTableExampleComponent {}Install
npx base-ui-cli add data-table
Set resizable and drag the right edge of a header. Lock a column with resizable: false on the column definition. Initial widths come from column.width.
ID | Name | Email | Role | Status | Joined |
|---|---|---|---|---|---|
| 1 | Alice Johnson | alice@example.com | Admin | Active | 2024-01-15 |
| 2 | Bob Smith | bob@example.com | Editor | Active | 2024-02-20 |
| 3 | Carol White | carol@example.com | Viewer | Inactive | 2024-03-10 |
| 4 | David Brown | david@example.com | Editor | Active | 2024-04-05 |
| 5 | Eve Davis | eve@example.com | Admin | Active | 2024-05-12 |
| 6 | Frank Miller | frank@example.com | Viewer | Active | 2024-06-18 |
<base-data-table resizable [columns]="columns" [data]="users"> </base-data-table>
TypeScript
import { Component } from '@angular/core';
// Install: npx base-ui-cli add data-table
// Paths are relative to aliases.components (default: src/app/components)
import { DataTableComponent } from './data-table/data-table.component';
import { TableCellContext, TableCellDirective } from './data-table/table-cell.directive';
import { TableEmptyDirective } from './data-table/table-empty.directive';
@Component({
selector: 'app-data-table-example',
imports: [
DataTableComponent,
TableCellDirective,
TableEmptyDirective
],
template: `...`
})
export class DataTableExampleComponent {}Install
npx base-ui-cli add data-table
Set serverSide so the table does not slice or sort locally. Pass the current page in data, the full count in totalItems, and reload from pageChange / sortChange. Drive those events from Angular httpResource in the httpResource cookbook. Below-fold tables defer with a skeleton in the @defer cookbook.
ID | Name | Email | Role | Status | Joined |
|---|---|---|---|---|---|
| 1 | User 1 | user1@example.com | Admin | Active | 2024-01-15 |
| 2 | User 2 | user2@example.com | Editor | Inactive | 2024-02-15 |
| 3 | User 3 | user3@example.com | Viewer | Active | 2024-03-15 |
| 4 | User 4 | user4@example.com | Admin | Inactive | 2024-04-15 |
| 5 | User 5 | user5@example.com | Editor | Active | 2024-05-15 |
| 6 | User 6 | user6@example.com | Viewer | Inactive | 2024-06-15 |
| 7 | User 7 | user7@example.com | Admin | Active | 2024-07-15 |
| 8 | User 8 | user8@example.com | Editor | Inactive | 2024-08-15 |
<base-data-table serverSide sortable pageable [pageSize]="8" [totalItems]="total" [data]="page" [loading]="loading" (pageChange)="fetch($event)" (sortChange)="sort($event)"> </base-data-table>
TypeScript
import { Component } from '@angular/core';
// Install: npx base-ui-cli add data-table
// Paths are relative to aliases.components (default: src/app/components)
import { DataTableComponent } from './data-table/data-table.component';
import { TableCellContext, TableCellDirective } from './data-table/table-cell.directive';
import { TableEmptyDirective } from './data-table/table-empty.directive';
@Component({
selector: 'app-data-table-example',
imports: [
DataTableComponent,
TableCellDirective,
TableEmptyDirective
],
template: `...`
})
export class DataTableExampleComponent {}Install
npx base-ui-cli add data-table
Set virtualize for large in-memory lists. Rows are windowed inside a fixed-height viewport. Pair with rowHeight matching the rendered row.
ID | Name | Email | Role | Status | Joined |
|---|---|---|---|---|---|
| 1 | User 1 | user1@example.com | Admin | Active | 2024-01-15 |
| 2 | User 2 | user2@example.com | Editor | Inactive | 2024-02-15 |
| 3 | User 3 | user3@example.com | Viewer | Active | 2024-03-15 |
| 4 | User 4 | user4@example.com | Admin | Inactive | 2024-04-15 |
| 5 | User 5 | user5@example.com | Editor | Active | 2024-05-15 |
| 6 | User 6 | user6@example.com | Viewer | Inactive | 2024-06-15 |
| 7 | User 7 | user7@example.com | Admin | Active | 2024-07-15 |
| 8 | User 8 | user8@example.com | Editor | Inactive | 2024-08-15 |
| 9 | User 9 | user9@example.com | Viewer | Active | 2024-09-15 |
| 10 | User 10 | user10@example.com | Admin | Inactive | 2024-10-15 |
| 11 | User 11 | user11@example.com | Editor | Active | 2024-11-15 |
| 12 | User 12 | user12@example.com | Viewer | Inactive | 2024-12-15 |
| 13 | User 13 | user13@example.com | Admin | Active | 2024-01-15 |
| 14 | User 14 | user14@example.com | Editor | Inactive | 2024-02-15 |
| 15 | User 15 | user15@example.com | Viewer | Active | 2024-03-15 |
| 16 | User 16 | user16@example.com | Admin | Inactive | 2024-04-15 |
<base-data-table virtualize [viewportHeight]="320" [rowHeight]="44" [columns]="columns" [data]="lotsOfRows"> </base-data-table>
TypeScript
import { Component } from '@angular/core';
// Install: npx base-ui-cli add data-table
// Paths are relative to aliases.components (default: src/app/components)
import { DataTableComponent } from './data-table/data-table.component';
import { TableCellContext, TableCellDirective } from './data-table/table-cell.directive';
import { TableEmptyDirective } from './data-table/table-empty.directive';
@Component({
selector: 'app-data-table-example',
imports: [
DataTableComponent,
TableCellDirective,
TableEmptyDirective
],
template: `...`
})
export class DataTableExampleComponent {}Install
npx base-ui-cli add data-table
Site-wide VPAT-style notes live on the accessibility report.
Unit tests: copy DataTableHarness with the component and load it with TestbedHarnessEnvironment.loader(fixture). See Getting started — Testing.
Run the following command to add this component to your project:
npx base-ui-cli add data-table
For AI agents
Copy a prompt with the registry name, CLI install, and import — or add the Base UI MCP server.
npx -y base-ui-ng-mcp
Base UI provides standalone components. Import the exact elements you want to use into your component.
// Install: npx base-ui-cli add data-table
// Paths are relative to aliases.components (default: src/app/components)
import { DataTableComponent } from './data-table/data-table.component';
import { TableCellContext, TableCellDirective } from './data-table/table-cell.directive';
import { TableEmptyDirective } from './data-table/table-empty.directive';
@Component({
selector: 'app-your-component',
imports: [
DataTableComponent,
TableCellDirective,
TableEmptyDirective
],
template: `...`
})
export class YourComponent {} API for data-table — generated from JSDoc in the library source.
| API | Member | Type | Default | Description |
|---|---|---|---|---|
TableCellContext | $implicit | T | — | — |
TableCellContext | row | T | — | — |
TableCellContext | column | TableColumn | — | — |
TableCellContext | value | unknown | — | — |
TableCellContext | index | number | — | — |
base-data-table | class | string | '' | Extra host classes merged via `cn()`. |
base-data-table | columns | TableColumn[] | [] | Column definitions (`key`, `label`, optional `sortable` / `width` / `align`). Cells default to `row[key]`. Override a column with `<ng-template baseTableCell="key">`. |
base-data-table | data | T[] | [] | Row objects. When `serverSide` is set, pass the current page only. |
base-data-table | sortable | boolean | false | Enables header click sorting for columns with `sortable: true`. |
base-data-table | pageable | boolean | false | Shows the paginator when the row count exceeds `pageSize`. |
base-data-table | pageSize | number | 10 | Rows per page (client slice, or the page size advertised to the server). |
base-data-table | loading | boolean | false | Shows skeleton rows instead of data. |
base-data-table | striped | boolean | false | Alternating row background. |
base-data-table | bordered | boolean | false | Outer and row borders. |
base-data-table | hoverable | boolean | true | Row hover highlight. Defaults to true. |
base-data-table | emptyMessage | string | — | Message shown when `data` is empty and not loading. Replaced by `baseTableEmpty` when projected. When omitted, uses `provideBaseUiI18n().noDataAvailable`. |
base-data-table | selectable | boolean | false | Adds a checkbox column. Bind `selected` to an array of `rowKey` values. |
base-data-table | rowKey | unknown | 'id' | Property used as the selection identity. Defaults to `'id'`. |
base-data-table | selected | (string | number)[] | [] | Selected row ids (or whatever `rowKey` points at). Two-way bindable. |
base-data-table | resizable | boolean | false | Drag the header edge to resize columns. Uses `column.width` as the start width. |
base-data-table | serverSide | boolean | false | Skip client sort/slice. Parent fetches; pass the current page in `data` and the full count in `totalItems`. |
base-data-table | totalItems | number | 0 | Total rows across all pages. Required for the paginator when `serverSide` is set. |
base-data-table | virtualize | boolean | false | Window rows in a fixed-height viewport. Best with `pageable` off and large `data`. |
base-data-table | rowHeight | number | 44 | Fixed row height in pixels used for windowing. Must match the rendered row. |
base-data-table | viewportHeight | number | 400 | Viewport height in pixels when `virtualize` is set. |
base-data-table | currentPage | number | 1 | Current page (1-based). Two-way bindable for server-driven tables. |
base-data-table | (rowClick) | T | — | Emits the clicked row. Checkbox clicks do not fire this. |
base-data-table | (sortChange) | DataTableSortChange | — | Emits after the user toggles a sortable header. |
base-data-table | (pageChange) | DataTablePageChange | — | Emits when the paginator changes page. Always fired in `serverSide` mode. |
base-data-table | (selectionChange) | T[] | — | Emits the selected row objects whenever the checkbox column changes. |
[baseTableCell] | baseTableCell | string | (required) | `TableColumn.key` this template renders. |
[baseTableEmpty] | — | — | — | Marks an `ng-template` as the empty-state body for . When present, it replaces `emptyMessage` in the empty row. |
Content