Taiga UI 5 is out!

Table ADDON-TABLE

Examples API GitHub

On this page

See also

TablePagination, TableFilters

This module allows you to create various tables, both static and editable.

Basic

Most minimalistic usage example.
NameBalance
Alex Inkin1 323 525
Roman Sedov423 242
    
      
    
    
      <table tuiTable>
    <thead>
        <tr>
            <th tuiTh>Name</th>
            <th tuiTh>Balance</th>
        </tr>
    </thead>
    <tbody tuiTbody>
        @for (item of data; track item) {
            <tr>
                <td tuiTd>{{ item.name }}</td>
                <td tuiTd>{{ item.balance | tuiFormatNumber }}</td>
            </tr>
        }
    </tbody>
</table>

    

Custom

Using various components within table cells.
Checkbox
TitleCellStatus Items ProgressActions
Data point 1 The first element
This is title Chip More information ・ Data
John Cleese silly@walk.uk
Success
Some
items
displayed
here
and
can
overflow
78ms
Some title Some more text
More info Chips can be here
Eric Idle cool@dude.com
Failure
One
Item
91ms
And now Completely different
Wow
Michael Palin its@man.com
Pending 32ms
    
      
    
    
      <div [style.margin-block-end.rem]="2">
    <tui-radio-list
        [itemContent]="content"
        [items]="sizes"
        [style.flex-direction]="'row'"
        [style.width]="'max-content'"
        [(ngModel)]="size"
    />
    <ng-template
        #content
        let-value
    >
        @let text = value === 'm' ? 'Medium' : 'Large';
        {{ value === 's' ? 'Small' : text }}
    </ng-template>
</div>

<table
    tuiTable
    [size]="size"
    [style.width.rem]="58"
    [(ngModel)]="selected"
>
    <thead>
        <tr>
            <th tuiTh>
                <div [tuiCell]="size">
                    <input
                        tuiCheckbox
                        tuiCheckboxTable
                        type="checkbox"
                        [size]="size === 'l' ? 'm' : 's'"
                    />
                    <span tuiTitle>Checkbox</span>
                </div>
            </th>
            <th tuiTh>Title</th>
            <th tuiTh>Cell</th>
            <th tuiTh>Status</th>
            <th
                tuiTh
                [style.width.rem]="10"
            >
                Items
            </th>
            <th tuiTh>Progress</th>
            <th tuiTh>Actions</th>
        </tr>
    </thead>
    <tbody tuiTbody>
        @for (item of data; track item) {
            <tr>
                <td tuiTd>
                    <div [tuiCell]="size">
                        <input
                            tuiCheckbox
                            type="checkbox"
                            [size]="size === 'l' ? 'm' : 's'"
                            [tuiCheckboxRow]="item"
                        />
                        <span tuiTitle>
                            {{ item.checkbox.title }}
                            <span tuiSubtitle>{{ item.checkbox.subtitle }}</span>
                        </span>
                    </div>
                </td>
                <td tuiTd>
                    <div [tuiCell]="size">
                        <span tuiTitle>
                            <span tuiStatus>
                                <tui-icon [icon]="item.title.icon" />
                                {{ item.title.title }}
                                @if (item.title.chip && item.title.subtitle) {
                                    <span tuiChip>{{ item.title.chip }}</span>
                                }
                            </span>
                            @if (!item.title.subtitle && item.title.chip) {
                                <span tuiChip>
                                    {{ item.title.chip }}
                                </span>
                            } @else {
                                <span tuiSubtitle>
                                    {{ item.title.subtitle }}
                                </span>
                            }
                        </span>
                    </div>
                </td>
                <td tuiTd>
                    <div [tuiCell]="size">
                        <div
                            [style.background]="item.cell.name | tuiAutoColor"
                            [tuiAvatar]="item.cell.name | tuiInitials"
                        ></div>
                        <span tuiTitle>
                            {{ item.cell.name }}
                            <span tuiSubtitle>{{ item.cell.email }}</span>
                        </span>
                    </div>
                </td>
                <td tuiTd>
                    <span [tuiStatus]="item.status.color">{{ item.status.value }}</span>
                </td>
                <td tuiTd>
                    <tui-items-with-more>
                        @for (chip of item.items; track chip) {
                            <div
                                *tuiItem
                                tuiBadge
                                [style.margin-inline-end.rem]="0.25"
                            >
                                {{ chip }}
                            </div>
                        }
                        <ng-template
                            let-number
                            tuiMore
                        >
                            <button
                                appearance="action-grayscale"
                                tuiDropdownAlign="end"
                                tuiDropdownAuto
                                tuiLink
                                type="button"
                                [style.text-decoration-style]="'dashed'"
                                [tuiDropdown]="dropdown"
                            >
                                + {{ item.items.length - number - 1 }}
                            </button>
                            <ng-template #dropdown>
                                <div
                                    tuiItemGroup
                                    [style.padding]="'1rem 0.75rem 0.75rem 1rem'"
                                >
                                    @for (chip of item.items; track chip) {
                                        @if ($index > number) {
                                            <div tuiBadge>{{ chip }}</div>
                                        }
                                    }
                                </div>
                            </ng-template>
                        </ng-template>
                    </tui-items-with-more>
                </td>
                <td tuiTd>
                    <span tuiStatus>
                        <progress
                            tuiProgressBar
                            [style.width.rem]="6"
                            [value]="item.progress / 100"
                        ></progress>
                        {{ item.progress }}ms
                    </span>
                </td>
                <td tuiTd>
                    <span tuiStatus>
                        <button
                            appearance="action"
                            iconStart="@tui.pencil"
                            size="xs"
                            tuiIconButton
                            type="button"
                        >
                            Edit
                        </button>
                        <button
                            appearance="action"
                            iconStart="@tui.ellipsis"
                            size="xs"
                            tuiIconButton
                            type="button"
                        >
                            More
                        </button>
                    </span>
                </td>
            </tr>
        }
    </tbody>
</table>

    
    
      [tuiTh],
[tuiTd] {
    border-inline-start: none;
    border-inline-end: none;
}

[tuiTable][data-size='s'] [tuiTitle] {
    flex-direction: row;
    gap: 0.375rem;
}

    

Editable

Using editable textfield controls inside a table.
Name Purchase
999 999
998
149.75
19 999
14 997
74.75
    
      
    
    
      <tui-scrollbar
    waIntersectionRoot
    class="scrollbar"
>
    <table
        size="l"
        tuiTable
        class="table"
        [columns]="columns"
        [direction]="-1"
        [sorter]="totalSorter"
    >
        <thead tuiThead>
            <tr tuiThGroup>
                <th
                    *tuiHead="'name'"
                    rowspan="2"
                    tuiTh
                    class="first"
                    [sorter]="null"
                    [sticky]="true"
                >
                    Name
                </th>
                <th
                    *tuiHead="'price'"
                    rowspan="2"
                    tuiTh
                    class="number second"
                    [sticky]="true"
                >
                    Price,&nbsp;$
                </th>
                <th
                    *tuiHead="'quantity'"
                    colspan="2"
                    tuiTh
                    [sorter]="null"
                >
                    Purchase
                </th>
                <ng-container *tuiHead="'unit'" />
                <th
                    *tuiHead="'date'"
                    rowspan="2"
                    tuiTh
                    [minWidth]="155"
                >
                    Date
                </th>
                <th
                    *tuiHead="'total'"
                    rowspan="2"
                    tuiTh
                    class="number"
                    [sorter]="totalSorter"
                >
                    Total
                </th>
            </tr>
            <tr tuiThGroup>
                <th
                    *tuiHead="'quantity'"
                    tuiTh
                    class="number border"
                >
                    Quantity
                </th>
                <th
                    *tuiHead="'unit'"
                    tuiTh
                >
                    Units
                </th>
            </tr>
        </thead>
        @let sortedPythons = pythons | tuiTableSort;
        <tbody
            heading="Monty Python"
            tuiTbody
            [data]="sortedPythons"
        >
            @for (item of sortedPythons; track trackByIndex($index)) {
                <tr tuiTr>
                    <th
                        *tuiCell="'name'"
                        tuiTd
                        [colSpan]="item.price > 1000 ? 2 : 0"
                    >
                        <tui-textfield>
                            <textarea
                                tuiTextarea
                                [ngModel]="item.name"
                                [ngModelOptions]="options"
                                (ngModelChange)="onValueChange($event, 'name', item, pythons)"
                            ></textarea>
                        </tui-textfield>
                    </th>
                    <ng-container *tuiCell="'price'">
                        @if (item.price <= 1000) {
                            <th
                                tuiTd
                                class="second"
                            >
                                <tui-textfield>
                                    <input
                                        tuiInputNumber
                                        class="number"
                                        [ngModel]="item.price"
                                        [ngModelOptions]="options"
                                        [tuiValidator]="minPrice"
                                        (ngModelChange)="onValueChange($event, 'price', item, pythons)"
                                    />
                                </tui-textfield>
                            </th>
                        }
                    </ng-container>
                    <td
                        *tuiCell="'quantity'"
                        tuiTd
                    >
                        <tui-textfield>
                            <input
                                tuiInputNumber
                                [ngModel]="item.quantity"
                                [ngModelOptions]="options"
                                [tuiNumberFormat]="{precision: 0}"
                                (ngModelChange)="onValueChange($event, 'quantity', item, pythons)"
                            />
                        </tui-textfield>
                    </td>
                    <td
                        *tuiCell="'unit'"
                        tuiTd
                    >
                        <tui-textfield
                            tuiChevron
                            class="select"
                        >
                            <input
                                placeholder="Units"
                                tuiSelect
                                [ngModel]="item.unit"
                                [ngModelOptions]="options"
                                (ngModelChange)="onValueChange($event, 'unit', item, pythons)"
                            />
                            <tui-data-list-wrapper
                                *tuiDropdown
                                [items]="units"
                            />
                        </tui-textfield>
                    </td>
                    <td
                        *tuiCell="'date'"
                        tuiTd
                    >
                        <tui-textfield>
                            <input
                                tuiInputDate
                                [ngModel]="item.date"
                                [ngModelOptions]="options"
                                (ngModelChange)="onValueChange($event, 'date', item, pythons)"
                            />
                            <tui-calendar *tuiDropdown />
                        </tui-textfield>
                    </td>
                    <td
                        *tuiCell="'total'"
                        tuiTd
                        class="number text"
                    >
                        {{ getTotal(item) | tuiFormatNumber }}
                    </td>
                </tr>
            }
        </tbody>
        @let sortedStarwars = starwars | tuiTableSort;
        <tbody
            tuiTbody
            [data]="sortedStarwars"
            [heading]="template"
        >
            @for (item of sortedStarwars; track trackByIndex($index)) {
                <tr tuiTr>
                    <th
                        *tuiCell="'name'"
                        tuiTd
                    >
                        <tui-textfield>
                            <textarea
                                tuiTextarea
                                [ngModel]="item.name"
                                [ngModelOptions]="options"
                                (ngModelChange)="onValueChange($event, 'name', item, starwars)"
                            ></textarea>
                        </tui-textfield>
                    </th>
                    <th
                        *tuiCell="'price'"
                        tuiTd
                        class="second"
                    >
                        <tui-textfield>
                            <input
                                tuiInputNumber
                                [ngModel]="item.price"
                                [ngModelOptions]="options"
                                [tuiValidator]="minPrice"
                                (ngModelChange)="onValueChange($event, 'price', item, starwars)"
                            />
                        </tui-textfield>
                    </th>
                    <td
                        *tuiCell="'quantity'"
                        tuiTd
                    >
                        <tui-textfield>
                            <input
                                tuiInputNumber
                                [ngModel]="item.quantity"
                                [ngModelOptions]="options"
                                [tuiNumberFormat]="{precision: 0}"
                                (ngModelChange)="onValueChange($event, 'quantity', item, starwars)"
                            />
                        </tui-textfield>
                    </td>
                    <td
                        *tuiCell="'unit'"
                        tuiTd
                    >
                        <tui-textfield
                            tuiChevron
                            class="select"
                        >
                            <input
                                placeholder="Units"
                                tuiSelect
                                [ngModel]="item.unit"
                                [ngModelOptions]="options"
                                (ngModelChange)="onValueChange($event, 'unit', item, starwars)"
                            />
                            <tui-data-list-wrapper
                                *tuiDropdown
                                [items]="units"
                            />
                        </tui-textfield>
                    </td>
                    <td
                        *tuiCell="'date'"
                        tuiTd
                    >
                        <tui-textfield>
                            <input
                                tuiInputDate
                                [ngModel]="item.date"
                                [ngModelOptions]="options"
                                (ngModelChange)="onValueChange($event, 'date', item, starwars)"
                            />
                            <tui-calendar *tuiDropdown />
                        </tui-textfield>
                    </td>
                    <td
                        *tuiCell="'total'"
                        tuiTd
                        class="number text"
                    >
                        {{ getTotal(item) | tuiFormatNumber }}
                    </td>
                </tr>
            }
        </tbody>
    </table>
</tui-scrollbar>
<ng-template #template>
    <tui-icon
        icon="@tui.star"
        class="tui-space_right-3"
    />
    Star Wars
</ng-template>

    
    
      .table {
    table-layout: fixed;
}

.number {
    text-align: end;
    flex-direction: row-reverse;
}

.first {
    min-inline-size: 11rem;
    max-inline-size: 11rem;
}

.second {
    inset-inline-start: 11rem;
}

.text {
    vertical-align: top;
    padding-block-start: 1rem;
}

// Due to rowSpan this item appears to be the first child
// but it shouldn't have the left border in reality
.border {
    border-inline-start: none;
}

.select {
    inline-size: 6.25rem;
}

.scrollbar {
    max-block-size: 18.75rem;
}

    

Sorting

Using SortBy directive to work with column titles instead of manual sorters.

Sort key: name, direction: -1

Date of Birth
Pages 1 Lines per page of 1
    
      
    
    
      <p
    tuiTextfieldSize="m"
    class="filters"
>
    <tui-textfield class="input">
        <label tuiLabel>Find on page</label>
        <input
            tuiInput
            [(ngModel)]="search"
        />
    </tui-textfield>

    <tui-textfield>
        <label tuiLabel>Minimum age</label>
        <input
            tuiInputNumber
            [formControl]="minAge"
            [tuiNumberFormat]="{precision: 0}"
        />
    </tui-textfield>
</p>
<p class="filters">
    <label tuiLabel>
        <input
            tuiCheckbox
            type="checkbox"
            [(ngModel)]="dob"
        />
        Enable sorting by DOB
    </label>
    <button
        size="m"
        tuiButton
        tuiChevron
        tuiDropdownAuto
        type="button"
        [tuiDropdown]="dropdown"
    >
        Columns
    </button>
    <ng-template #dropdown>
        <tui-reorder
            class="columns"
            [enabled]="enabled"
            [(items)]="initial"
            (enabledChange)="onEnabled($event)"
        />
    </ng-template>
</p>
<tui-loader
    [loading]="!!(loading$ | async)"
    [overlay]="true"
>
    <p>
        <b>Sort key:</b>
        {{ sortKey$ | async }},
        <b>direction:</b>
        {{ direction$ | async }}
    </p>

    @if (data$ | async; as data) {
        <table
            tuiTable
            class="table"
            [columns]="columns"
            [direction]="(direction$ | async) ?? -1"
            [tuiSortBy]="sortKey$ | async"
            (tuiSortChange)="change($event)"
        >
            <thead>
                <tr tuiThGroup>
                    <th
                        *tuiHead="'name'"
                        tuiSortable
                        tuiTh
                        [requiredSort]="true"
                    >
                        Name
                    </th>
                    <th
                        *tuiHead="'dob'"
                        tuiTh
                        [tuiSortable]="dob"
                    >
                        Date of Birth
                    </th>
                    <th
                        *tuiHead="'age'"
                        tuiSortable
                        tuiTh
                        [requiredSort]="true"
                    >
                        Age
                    </th>
                </tr>
            </thead>
            @let sortedData = data | tuiTableSort;
            <tbody
                tuiTbody
                [data]="sortedData"
            >
                @for (item of sortedData; track item) {
                    <tr tuiTr>
                        <td
                            *tuiCell="'name'"
                            tuiTd
                            [class.match]="isMatch(item.name)"
                        >
                            {{ item.name }}
                        </td>
                        <td
                            *tuiCell="'dob'"
                            tuiTd
                            [class.match]="isMatch(item.dob)"
                        >
                            {{ item.dob }}
                        </td>
                        <td
                            *tuiCell="'age'"
                            tuiTd
                            [class.match]="isMatch(item.age)"
                        >
                            {{ item.age }}
                        </td>
                    </tr>
                }
            </tbody>
            <tfoot>
                <tr>
                    <td [colSpan]="columns.length">
                        <tui-table-pagination
                            class="tui-space_top-2"
                            [page]="(page$ | async) || 0"
                            [total]="(total$ | async) || 0"
                            (paginationChange)="onPagination($event)"
                        />
                    </td>
                </tr>
            </tfoot>
        </table>
    }
</tui-loader>

    
    
      .table {
    inline-size: 100%;
}

.filters {
    display: flex;
    gap: 1rem;
    white-space: nowrap;
    align-items: center;
}

.input {
    flex: 1;
}

.columns {
    inline-size: 10.625rem;
}

.match {
    background: var(--tui-service-selection-background);
}

    

Virtual scroll

Using ScrollingModule from @angular/cdk/scrolling to implement virtual scroll and tuiSorter pipe for easy sorting by key.
    
      
    

Columns

Using structural directives for dynamic columns.
1 name
    
      
    

Footer

Using caption tag to place pagination at the bottom of the table.
999 rows
NameBalance
Alex Inkin1 323 525
Roman Sedov423 242
    
      
    

Resizing

Making column headers draggable at the edge and setting width limits.
Name
Items
Balance
Alex Inkin
WalletPhone
Roman Sedov
Wallet
    
      
    

Expandable rows

Animated expandable sections of the table.
#NameDate of Birth
1John Matrix30.07.1947
2John Rambo04.07.1946
3John McClane19.03.1955
    
      
    

Toggle rows

More complex examples of expandable sections.
First name Last name Role Balance
3 Developers (basic usage) dev 5 970 009
Alex
Inkin dev 1 323 525
Roman
Sedov dev 423 242
Andrei
Serebrennikov dev 4 223 242
2 Designers (manual handling) design 4 646 484
Custom content (click on chevron) all 10 616 493
Alex Inkin Roman Sedov Andrei Serebrennikov Joe Wilson Julia Johnson
    
      
    

Controls

All unique textfield controls in their table representation.
Growing height Static height