Table
The data-table system: sorting, search, filters, pagination, column toggle and row selection on top of a plain HTML table. This page covers local mode — the dataset is read from the DOM. Link ng_table.js.
A ng-table root wraps an optional header and filter bar, plus a ng-table-wrapper with the actual ng-table-table. On init the JS reads the columns from data-col-key on each th and snapshots the tbody rows into an internal dataset; every feature (sort, search, filters, paging) runs on that dataset through one render pipeline. With data-table-source the same component switches to remote mode (AJAX).
Basic
The minimal shape: wrapper + table, a data-col-key per column. Give every column a width class from the ng-tcol-w-* system, and put ng-col-grow on the flexible one (e.g. Name or Email) so it fills the remaining space — a column left without a class auto-sizes to its content and throws the others off:
| ID | Name | Status | |
|---|---|---|---|
| 1 | Alice Rossi | alice.rossi@email.com | Active |
| 2 | Bruno Conti | bruno.conti@email.com | Suspended |
| 3 | Carla Greco | carla.greco@email.com | Blocked |
| 4 | Dario Bruno | dario.bruno@email.com | Active |
<div class="ng-table">
<div class="ng-table-wrapper">
<table class="ng-table-table">
<thead>
<tr>
<th class="ng-tcol-w-5 ng-tcol-center" data-col-key="id">ID</th>
<th class="ng-tcol-w-25" data-col-key="name">Name</th>
<th class="ng-col-grow" data-col-key="mail">Email</th>
<th class="ng-tcol-w-10 ng-tcol-center" data-col-key="status">Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Alice Rossi</td>
<td class="ng-tcol-trunc">alice.rossi@email.com</td>
<td><span class="ng-badge ng-badge-success">Active</span></td>
</tr>
<tr>
<td>2</td>
<td>Bruno Conti</td>
<td class="ng-tcol-trunc">bruno.conti@email.com</td>
<td><span class="ng-badge ng-badge-warning">Suspended</span></td>
</tr>
<tr>
<td>3</td>
<td>Carla Greco</td>
<td class="ng-tcol-trunc">carla.greco@email.com</td>
<td><span class="ng-badge ng-badge-danger">Blocked</span></td>
</tr>
<tr>
<td>4</td>
<td>Dario Bruno</td>
<td class="ng-tcol-trunc">dario.bruno@email.com</td>
<td><span class="ng-badge ng-badge-success">Active</span></td>
</tr>
</tbody>
</table>
</div>
</div>
Sorting
Make a header sortable with ng-sortable + a ng-sort span holding the ng-sort-indicator. data-sort-type picks the comparator (string, number, date with data-sort-locale eu/us); clicking cycles asc → desc → none, keyboard included. data-table-sort="key:dir" on the root sets the initial sort:
| Name | Orders | Joined |
|---|---|---|
| Alice Rossi | 12 | 04/02/2025 |
| Bruno Conti | 3 | 17/11/2024 |
| Carla Greco | 0 | 29/06/2026 |
| Dario Bruno | 7 | 11/01/2025 |
| Elena Ferri | 5 | 22/09/2024 |
<div class="ng-table" data-table-sort="orders:desc">
<div class="ng-table-wrapper">
<table class="ng-table-table">
<thead>
<tr>
<th class="ng-sortable ng-col-grow" data-col-key="name" data-sort-type="string">
<span class="ng-sort">Name <span class="ng-sort-indicator"></span></span>
</th>
<th class="ng-sortable ng-tcol-num ng-tcol-w-15" data-col-key="orders" data-sort-type="number">
<span class="ng-sort">Orders <span class="ng-sort-indicator"></span></span>
</th>
<th class="ng-sortable ng-tcol-w-20" data-col-key="joined" data-sort-type="date" data-sort-locale="eu">
<span class="ng-sort">Joined <span class="ng-sort-indicator"></span></span>
</th>
</tr>
</thead>
<tbody>
<tr>
<td>Alice Rossi</td>
<td class="ng-tcol-num">12</td>
<td>04/02/2025</td>
</tr>
<tr>
<td>Bruno Conti</td>
<td class="ng-tcol-num">3</td>
<td>17/11/2024</td>
</tr>
<tr>
<td>Carla Greco</td>
<td class="ng-tcol-num">0</td>
<td>29/06/2026</td>
</tr>
<tr>
<td>Dario Bruno</td>
<td class="ng-tcol-num">7</td>
<td>11/01/2025</td>
</tr>
<tr>
<td>Elena Ferri</td>
<td class="ng-tcol-num">5</td>
<td>22/09/2024</td>
</tr>
</tbody>
</table>
</div>
</div>
Search & count
An input with data-table-search anywhere inside the root filters all columns live (data-table-search-debounce in ms); an element with data-table-count shows the visible/filtered counter. No matches renders the "No results found" row (text overridable via data-table-text-no-results):
| ID | Name | Status | |
|---|---|---|---|
| 1 | Alice Rossi | alice.rossi@email.com | Active |
| 2 | Bruno Conti | bruno.conti@email.com | Suspended |
| 3 | Carla Greco | carla.greco@email.com | Blocked |
| 4 | Dario Bruno | dario.bruno@email.com | Active |
| 5 | Elena Ferri | elena.ferri@email.com | Active |
| 6 | Fabio Marino | fabio.marino@email.com | Suspended |
<div class="ng-table" data-table-search-debounce="250">
<div class="ng-table-filters">
<div class="ng-table-filters-right">
<span class="ng-badge ng-badge-primary-outline" data-table-count></span>
<input type="text" class="ng-input" placeholder="Search people..." data-table-search>
</div>
</div>
<div class="ng-table-wrapper">
<table class="ng-table-table">
<thead>
<tr>
<th class="ng-tcol-w-5 ng-tcol-center" data-col-key="id">ID</th>
<th class="ng-tcol-w-20" data-col-key="name">Name</th>
<th class="ng-col-grow" data-col-key="mail">Email</th>
<th class="ng-tcol-w-10 ng-tcol-center" data-col-key="status">Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Alice Rossi</td>
<td class="ng-tcol-trunc">alice.rossi@email.com</td>
<td><span class="ng-badge ng-badge-success">Active</span></td>
</tr>
<tr>
<td>2</td>
<td>Bruno Conti</td>
<td class="ng-tcol-trunc">bruno.conti@email.com</td>
<td><span class="ng-badge ng-badge-warning">Suspended</span></td>
</tr>
<tr>
<td>3</td>
<td>Carla Greco</td>
<td class="ng-tcol-trunc">carla.greco@email.com</td>
<td><span class="ng-badge ng-badge-danger">Blocked</span></td>
</tr>
<tr>
<td>4</td>
<td>Dario Bruno</td>
<td class="ng-tcol-trunc">dario.bruno@email.com</td>
<td><span class="ng-badge ng-badge-success">Active</span></td>
</tr>
<tr>
<td>5</td>
<td>Elena Ferri</td>
<td class="ng-tcol-trunc">elena.ferri@email.com</td>
<td><span class="ng-badge ng-badge-success">Active</span></td>
</tr>
<tr>
<td>6</td>
<td>Fabio Marino</td>
<td class="ng-tcol-trunc">fabio.marino@email.com</td>
<td><span class="ng-badge ng-badge-warning">Suspended</span></td>
</tr>
</tbody>
</table>
</div>
</div>
Filters
The ng-table-filters bar (with -left/-right areas) hosts ng-filter fields. Any select/input/radio with data-table-filter="colKey" filters that column by exact match — empty value means "all". ng-filter-inline lays label and controls on one row:
| Name | Orders | Status |
|---|---|---|
| Alice Rossi | 12 | Active |
| Bruno Conti | 3 | Suspended |
| Carla Greco | 0 | Blocked |
| Dario Bruno | 7 | Active |
| Elena Ferri | 5 | Active |
| Fabio Marino | 9 | Suspended |
<div class="ng-table">
<div class="ng-table-filters">
<div class="ng-table-filters-left">
<div class="ng-filter">
<select class="ng-select ng-filter-select" data-table-filter="status">
<option value="">All statuses</option>
<option value="Active">Active</option>
<option value="Suspended">Suspended</option>
<option value="Blocked">Blocked</option>
</select>
</div>
<div class="ng-filter ng-filter-inline">
<label class="ng-filter-label">Orders</label>
<label><input type="radio" name="fOrders" data-table-filter="orders" value="" checked>All</label>
<label><input type="radio" name="fOrders" data-table-filter="orders" value="0">None</label>
</div>
</div>
</div>
<div class="ng-table-wrapper">
<table class="ng-table-table">
<thead>
<tr>
<th class="ng-col-grow" data-col-key="name">Name</th>
<th class="ng-tcol-num ng-tcol-w-15" data-col-key="orders">Orders</th>
<th class="ng-tcol-w-10 ng-tcol-center" data-col-key="status">Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>Alice Rossi</td>
<td class="ng-tcol-num">12</td>
<td><span class="ng-badge ng-badge-success">Active</span></td>
</tr>
<tr>
<td>Bruno Conti</td>
<td class="ng-tcol-num">3</td>
<td><span class="ng-badge ng-badge-warning">Suspended</span></td>
</tr>
<tr>
<td>Carla Greco</td>
<td class="ng-tcol-num">0</td>
<td><span class="ng-badge ng-badge-danger">Blocked</span></td>
</tr>
<tr>
<td>Dario Bruno</td>
<td class="ng-tcol-num">7</td>
<td><span class="ng-badge ng-badge-success">Active</span></td>
</tr>
<tr>
<td>Elena Ferri</td>
<td class="ng-tcol-num">5</td>
<td><span class="ng-badge ng-badge-success">Active</span></td>
</tr>
<tr>
<td>Fabio Marino</td>
<td class="ng-tcol-num">9</td>
<td><span class="ng-badge ng-badge-warning">Suspended</span></td>
</tr>
</tbody>
</table>
</div>
</div>
Pagination & page size
data-table-limit turns paging on; the pagination bar is generated by the component (standard ng-pagination markup). data-table-page-size="3,5,10" adds a rows-per-page select (data-table-page-size-position: top/bottom/both), data-table-pagination-align aligns the bar:
| ID | Name | Status | |
|---|---|---|---|
| 1 | Alice Rossi | alice.rossi@email.com | Active |
| 2 | Bruno Conti | bruno.conti@email.com | Suspended |
| 3 | Carla Greco | carla.greco@email.com | Blocked |
| 4 | Dario Bruno | dario.bruno@email.com | Active |
| 5 | Elena Ferri | elena.ferri@email.com | Active |
| 6 | Fabio Marino | fabio.marino@email.com | Suspended |
| 7 | Giulia Costa | giulia.costa@email.com | Active |
| 8 | Luca Neri | luca.neri@email.com | Active |
<div class="ng-table"
data-table-limit="3"
data-table-page-size="3,5,10"
data-table-page-size-position="bottom"
data-table-pagination-align="left">
<div class="ng-table-wrapper">
<table class="ng-table-table">
<thead>
<tr>
<th class="ng-tcol-w-5 ng-tcol-center" data-col-key="id">ID</th>
<th class="ng-tcol-w-20" data-col-key="name">Name</th>
<th class="ng-col-grow" data-col-key="mail">Email</th>
<th class="ng-tcol-w-10 ng-tcol-center" data-col-key="status">Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Alice Rossi</td>
<td class="ng-tcol-trunc">alice.rossi@email.com</td>
<td><span class="ng-badge ng-badge-success">Active</span></td>
</tr>
<tr>
<td>2</td>
<td>Bruno Conti</td>
<td class="ng-tcol-trunc">bruno.conti@email.com</td>
<td><span class="ng-badge ng-badge-warning">Suspended</span></td>
</tr>
<tr>
<td>3</td>
<td>Carla Greco</td>
<td class="ng-tcol-trunc">carla.greco@email.com</td>
<td><span class="ng-badge ng-badge-danger">Blocked</span></td>
</tr>
<tr>
<td>4</td>
<td>Dario Bruno</td>
<td class="ng-tcol-trunc">dario.bruno@email.com</td>
<td><span class="ng-badge ng-badge-success">Active</span></td>
</tr>
<tr>
<td>5</td>
<td>Elena Ferri</td>
<td class="ng-tcol-trunc">elena.ferri@email.com</td>
<td><span class="ng-badge ng-badge-success">Active</span></td>
</tr>
<tr>
<td>6</td>
<td>Fabio Marino</td>
<td class="ng-tcol-trunc">fabio.marino@email.com</td>
<td><span class="ng-badge ng-badge-warning">Suspended</span></td>
</tr>
<tr>
<td>7</td>
<td>Giulia Costa</td>
<td class="ng-tcol-trunc">giulia.costa@email.com</td>
<td><span class="ng-badge ng-badge-success">Active</span></td>
</tr>
<tr>
<td>8</td>
<td>Luca Neri</td>
<td class="ng-tcol-trunc">luca.neri@email.com</td>
<td><span class="ng-badge ng-badge-success">Active</span></td>
</tr>
</tbody>
</table>
</div>
</div>
Row behavior
Modifiers on the ng-table-table: zebra striping, row-hover highlight, row-clickable cursor. Rows carrying data-row-id become selectable — click toggles is-selected (single selection, delegated listener):
| ID | Name | Status | |
|---|---|---|---|
| 1 | Alice Rossi | alice.rossi@email.com | Active |
| 2 | Bruno Conti | bruno.conti@email.com | Suspended |
| 3 | Carla Greco | carla.greco@email.com | Blocked |
| 4 | Dario Bruno | dario.bruno@email.com | Active |
| 5 | Elena Ferri | elena.ferri@email.com | Active |
<div class="ng-table">
<div class="ng-table-wrapper">
<table class="ng-table-table zebra row-hover row-clickable">
<thead>
<tr>
<th class="ng-tcol-w-5 ng-tcol-center" data-col-key="id">ID</th>
<th class="ng-tcol-w-20" data-col-key="name">Name</th>
<th class="ng-col-grow" data-col-key="mail">Email</th>
<th class="ng-tcol-w-10 ng-tcol-center" data-col-key="status">Status</th>
</tr>
</thead>
<tbody>
<tr data-row-id="1">
<td>1</td>
<td>Alice Rossi</td>
<td class="ng-tcol-trunc">alice.rossi@email.com</td>
<td><span class="ng-badge ng-badge-success">Active</span></td>
</tr>
<tr data-row-id="2">
<td>2</td>
<td>Bruno Conti</td>
<td class="ng-tcol-trunc">bruno.conti@email.com</td>
<td><span class="ng-badge ng-badge-warning">Suspended</span></td>
</tr>
<tr data-row-id="3">
<td>3</td>
<td>Carla Greco</td>
<td class="ng-tcol-trunc">carla.greco@email.com</td>
<td><span class="ng-badge ng-badge-danger">Blocked</span></td>
</tr>
<tr data-row-id="4">
<td>4</td>
<td>Dario Bruno</td>
<td class="ng-tcol-trunc">dario.bruno@email.com</td>
<td><span class="ng-badge ng-badge-success">Active</span></td>
</tr>
<tr data-row-id="5">
<td>5</td>
<td>Elena Ferri</td>
<td class="ng-tcol-trunc">elena.ferri@email.com</td>
<td><span class="ng-badge ng-badge-success">Active</span></td>
</tr>
</tbody>
</table>
</div>
</div>
Column toggle
A ng-dropdown inside the root with one checkbox per value="colKey" shows/hides columns (the table styles the checkboxes itself):
<div class="ng-table">
<div class="ng-table-filters">
<div class="ng-table-filters-right">
<div class="ng-dropdown align-left">
<a class="ng-dropdown-trigger"><i class="ph ph-rows fs-2xl text-primary"></i></a>
<div class="ng-dropdown-menu">
<label class="ng-dropdown-item"><input type="checkbox" value="mail" checked>Email</label>
<label class="ng-dropdown-item"><input type="checkbox" value="status" checked>Status</label>
</div>
</div>
</div>
</div>
<div class="ng-table-wrapper">
<table class="ng-table-table">
<thead>
<tr>
<th class="ng-tcol-w-5 ng-tcol-center" data-col-key="id">ID</th>
<th class="ng-tcol-w-20" data-col-key="name">Name</th>
<th class="ng-col-grow" data-col-key="mail">Email</th>
<th class="ng-tcol-w-10 ng-tcol-center" data-col-key="status">Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Alice Rossi</td>
<td class="ng-tcol-trunc">alice.rossi@email.com</td>
<td><span class="ng-badge ng-badge-success">Active</span></td>
</tr>
<tr>
<td>2</td>
<td>Bruno Conti</td>
<td class="ng-tcol-trunc">bruno.conti@email.com</td>
<td><span class="ng-badge ng-badge-warning">Suspended</span></td>
</tr>
<tr>
<td>3</td>
<td>Carla Greco</td>
<td class="ng-tcol-trunc">carla.greco@email.com</td>
<td><span class="ng-badge ng-badge-danger">Blocked</span></td>
</tr>
<tr>
<td>4</td>
<td>Dario Bruno</td>
<td class="ng-tcol-trunc">dario.bruno@email.com</td>
<td><span class="ng-badge ng-badge-success">Active</span></td>
</tr>
</tbody>
</table>
</div>
</div>
Scroll & sticky header
The thead is sticky by design — pick the scroll mode: scroll-y keeps a fixed height (--ng-table-max-h, default 420px) and scrolls inside the wrapper; sticky-page lets the page scroll and pins the header at --ng-table-sticky-top (e.g. under a fixed app header — note: no horizontal scroll in that mode):
| ID | Name | |
|---|---|---|
| 1 | Alice Rossi | alice.rossi@email.com |
| 2 | Bruno Conti | bruno.conti@email.com |
| 3 | Carla Greco | carla.greco@email.com |
| 4 | Dario Bruno | dario.bruno@email.com |
| 5 | Elena Ferri | elena.ferri@email.com |
| 6 | Fabio Marino | fabio.marino@email.com |
| 7 | Giulia Costa | giulia.costa@email.com |
| 8 | Luca Neri | luca.neri@email.com |
<div class="ng-table scroll-y" style="--ng-table-max-h: 230px">
<div class="ng-table-wrapper">
<table class="ng-table-table">
<thead>
<tr>
<th class="ng-tcol-w-5 ng-tcol-center" data-col-key="id">ID</th>
<th class="ng-tcol-w-25" data-col-key="name">Name</th>
<th class="ng-col-grow" data-col-key="mail">Email</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Alice Rossi</td>
<td class="ng-tcol-trunc">alice.rossi@email.com</td>
</tr>
<tr>
<td>2</td>
<td>Bruno Conti</td>
<td class="ng-tcol-trunc">bruno.conti@email.com</td>
</tr>
<tr>
<td>3</td>
<td>Carla Greco</td>
<td class="ng-tcol-trunc">carla.greco@email.com</td>
</tr>
<tr>
<td>4</td>
<td>Dario Bruno</td>
<td class="ng-tcol-trunc">dario.bruno@email.com</td>
</tr>
<tr>
<td>5</td>
<td>Elena Ferri</td>
<td class="ng-tcol-trunc">elena.ferri@email.com</td>
</tr>
<tr>
<td>6</td>
<td>Fabio Marino</td>
<td class="ng-tcol-trunc">fabio.marino@email.com</td>
</tr>
<tr>
<td>7</td>
<td>Giulia Costa</td>
<td class="ng-tcol-trunc">giulia.costa@email.com</td>
</tr>
<tr>
<td>8</td>
<td>Luca Neri</td>
<td class="ng-tcol-trunc">luca.neri@email.com</td>
</tr>
</tbody>
</table>
</div>
</div>
Complete example
Header, filter bar with count, search, column toggle and reset (via the instance API), sortable columns, zebra + hover + selection, paging with page-size select — the canonical full table:
| ID | Name | Orders | Status | Joined | |
|---|---|---|---|---|---|
| 1 | Alice Rossi | alice.rossi@email.com | 12 | Active | 04/02/2025 |
| 2 | Bruno Conti | bruno.conti@email.com | 3 | Suspended | 17/11/2024 |
| 3 | Carla Greco | carla.greco@email.com | 0 | Blocked | 29/06/2026 |
| 4 | Dario Bruno | dario.bruno@email.com | 7 | Active | 11/01/2025 |
| 5 | Elena Ferri | elena.ferri@email.com | 5 | Active | 22/09/2024 |
| 6 | Fabio Marino | fabio.marino@email.com | 9 | Suspended | 03/03/2026 |
| 7 | Giulia Costa | giulia.costa@email.com | 2 | Active | 15/12/2024 |
| 8 | Luca Neri | luca.neri@email.com | 14 | Active | 08/05/2025 |
<div class="ng-table" id="team-table"
data-table-sort="id:asc"
data-table-limit="5"
data-table-page-size="5,10,20"
data-table-page-size-position="bottom"
data-table-pagination-align="left"
data-table-search-debounce="250">
<div class="ng-table-header">
<div class="ng-table-head-left">
<div class="ng-table-title">Team members</div>
<div class="ng-table-subtitle">Accounts and activation status</div>
</div>
<div class="ng-table-head-right">
<a href="#" class="ng-btn ng-btn-primary">New</a>
<a href="#" class="ng-btn ng-btn-primary-outline">Export</a>
</div>
</div>
<div class="ng-table-filters">
<div class="ng-table-filters-left">
<div class="ng-filter">
<select class="ng-select ng-filter-select" data-table-filter="status">
<option value="">All statuses</option>
<option value="Active">Active</option>
<option value="Suspended">Suspended</option>
<option value="Blocked">Blocked</option>
</select>
</div>
</div>
<div class="ng-table-filters-right">
<span class="ng-badge ng-badge-primary-outline" data-table-count></span>
<input type="text" class="ng-input" placeholder="Search..." data-table-search>
<div class="ng-dropdown align-left">
<a class="ng-dropdown-trigger"><i class="ph ph-rows fs-2xl text-primary"></i></a>
<div class="ng-dropdown-menu">
<label class="ng-dropdown-item"><input type="checkbox" value="mail" checked>Email</label>
<label class="ng-dropdown-item"><input type="checkbox" value="orders" checked>Orders</label>
<label class="ng-dropdown-item"><input type="checkbox" value="joined" checked>Joined</label>
</div>
</div>
<a href="#" id="team-table-reset" aria-label="Reset table"><i class="ph-fill ph-funnel-x text-primary fs-xl"></i></a>
</div>
</div>
<div class="ng-table-wrapper">
<table class="ng-table-table zebra row-hover row-clickable">
<thead>
<tr>
<th class="ng-tcol-w-5 ng-tcol-center" data-col-key="id">ID</th>
<th class="ng-sortable ng-tcol-w-20" data-col-key="name" data-sort-type="string">
<span class="ng-sort">Name <span class="ng-sort-indicator"></span></span>
</th>
<th class="ng-col-grow" data-col-key="mail">Email</th>
<th class="ng-sortable ng-tcol-num ng-tcol-w-10" data-col-key="orders" data-sort-type="number">
<span class="ng-sort">Orders <span class="ng-sort-indicator"></span></span>
</th>
<th class="ng-tcol-w-10 ng-tcol-center" data-col-key="status">Status</th>
<th class="ng-sortable ng-tcol-w-15" data-col-key="joined" data-sort-type="date" data-sort-locale="eu">
<span class="ng-sort">Joined <span class="ng-sort-indicator"></span></span>
</th>
</tr>
</thead>
<tbody>
<tr data-row-id="1">
<td>1</td>
<td>Alice Rossi</td>
<td class="ng-tcol-trunc">alice.rossi@email.com</td>
<td class="ng-tcol-num">12</td>
<td><span class="ng-badge ng-badge-success">Active</span></td>
<td>04/02/2025</td>
</tr>
<tr data-row-id="2">
<td>2</td>
<td>Bruno Conti</td>
<td class="ng-tcol-trunc">bruno.conti@email.com</td>
<td class="ng-tcol-num">3</td>
<td><span class="ng-badge ng-badge-warning">Suspended</span></td>
<td>17/11/2024</td>
</tr>
<tr data-row-id="3">
<td>3</td>
<td>Carla Greco</td>
<td class="ng-tcol-trunc">carla.greco@email.com</td>
<td class="ng-tcol-num">0</td>
<td><span class="ng-badge ng-badge-danger">Blocked</span></td>
<td>29/06/2026</td>
</tr>
<tr data-row-id="4">
<td>4</td>
<td>Dario Bruno</td>
<td class="ng-tcol-trunc">dario.bruno@email.com</td>
<td class="ng-tcol-num">7</td>
<td><span class="ng-badge ng-badge-success">Active</span></td>
<td>11/01/2025</td>
</tr>
<tr data-row-id="5">
<td>5</td>
<td>Elena Ferri</td>
<td class="ng-tcol-trunc">elena.ferri@email.com</td>
<td class="ng-tcol-num">5</td>
<td><span class="ng-badge ng-badge-success">Active</span></td>
<td>22/09/2024</td>
</tr>
<tr data-row-id="6">
<td>6</td>
<td>Fabio Marino</td>
<td class="ng-tcol-trunc">fabio.marino@email.com</td>
<td class="ng-tcol-num">9</td>
<td><span class="ng-badge ng-badge-warning">Suspended</span></td>
<td>03/03/2026</td>
</tr>
<tr data-row-id="7">
<td>7</td>
<td>Giulia Costa</td>
<td class="ng-tcol-trunc">giulia.costa@email.com</td>
<td class="ng-tcol-num">2</td>
<td><span class="ng-badge ng-badge-success">Active</span></td>
<td>15/12/2024</td>
</tr>
<tr data-row-id="8">
<td>8</td>
<td>Luca Neri</td>
<td class="ng-tcol-trunc">luca.neri@email.com</td>
<td class="ng-tcol-num">14</td>
<td><span class="ng-badge ng-badge-success">Active</span></td>
<td>08/05/2025</td>
</tr>
</tbody>
</table>
</div>
</div>
<script>
document.getElementById('team-table-reset').addEventListener('click', (e) => {
e.preventDefault();
document.getElementById('team-table').reset();
});
</script>
Sub-table — expandable rows
Opt in with data-table-expandable on the root: the JS injects an expander column and a caret per row (each row needs a data-row-id). On the first expand the table fires ng:table:expand with { id, row, container } — fill container once (it's cached, no re-fetch on re-render/sort/filter). Add data-expand-single to keep only one detail row open.
| ID | Name | Status |
|---|---|---|
| 1 | Luca Bruno | Active |
| 2 | Marco Rossi | Suspended |
| 3 | Sara Neri | Active |
<div class="ng-table" id="tblExpand" data-table-expandable data-expand-single>
<div class="ng-table-wrapper">
<table class="ng-table-table row-hover">
<thead>
<tr>
<th class="ng-tcol-w-5 ng-tcol-center" data-col-key="id">ID</th>
<th class="ng-col-grow" data-col-key="name">Name</th>
<th class="ng-tcol-w-10 ng-tcol-center" data-col-key="status">Status</th>
</tr>
</thead>
<tbody>
<tr data-row-id="1"><td>1</td><td>Luca Bruno</td><td><span class="ng-badge ng-badge-success">Active</span></td></tr>
<tr data-row-id="2"><td>2</td><td>Marco Rossi</td><td><span class="ng-badge ng-badge-warning">Suspended</span></td></tr>
<tr data-row-id="3"><td>3</td><td>Sara Neri</td><td><span class="ng-badge ng-badge-success">Active</span></td></tr>
</tbody>
</table>
</div>
</div>
Populate the detail lazily via the event:
// fill the detail row lazily on FIRST expand (cached afterwards)
table.addEventListener('ng:table:expand', (e) => {
const { id, row, container } = e.detail;
container.innerHTML = renderDetail(row); // your content
});
// collapse → ng:table:collapse, detail { id, row }
Options
data-table-sort | Initial sort, "key:asc|desc". |
|---|---|
data-table-limit | Rows per page; absent = no pagination. |
data-table-page-size | Comma list for the rows-per-page select (e.g. 10,20,50). |
data-table-page-size-position | top · bottom · both (default). |
data-table-pagination-align | Pagination alignment (default left). |
data-table-search-debounce | Search debounce in ms (default 0). |
data-table-persist | Persists the view state in localStorage (ng-table:<id> — give the root an id). |
data-table-text-empty / -no-results / -error | Localizable state texts. |
data-table-source | Switches to remote mode — see Table (AJAX). |
th[data-col-key] | Column key (required per column). |
th[data-sort-type] / [data-sort-locale] | string · number · date; locale eu (default) · us. |
tr[data-row-id] | Enables single row selection for that row. |
data-table-expandable | Sub-table: expander column + caret per row (rows need data-row-id). |
data-expand-single | Only one detail row open at a time. |
data-expand-icon / -collapse-icon | Phosphor ph-* for the expander (default caret). |
data-expand-text / -collapse-text | Text trigger instead of the icon. |
data-expand-size | Expander size: sm · md · lg · xl. |
API
Each instance exposes root.__ngTable plus shortcuts directly on the root element:
const table = document.getElementById('team-table');
table.search('alice'); // full-text search (resets to page 1)
table.setPage(2); // go to page
table.setLimit(20); // rows per page
table.reset(); // clear sort/filters/search/selection, back to defaults
table.reload(); // re-run the render pipeline
table.getState(); // snapshot of the view state (page, sort, filters...)
table.export(); // current dataset as array of rows
// same API namespaced on the instance:
table.__ngTable.search('alice');
Anatomy
ng-table ng-table-header ng-table-head-left ng-table-head-right ng-table-title ng-table-subtitle ng-table-filters ng-table-filters-left ng-table-filters-right ng-filter ng-filter-label ng-filter-select ng-filter-inline ng-table-wrapper ng-table-table ng-table-pagination Table modifiers & cell utilities
zebra row-hover row-clickable is-selected scroll-y sticky-page ng-sortable ng-sort ng-sort-indicator ng-tcol-w-{n} ng-col-grow ng-tcol-trunc ng-tcol-mono ng-tcol-num Reference
Generated from the component source (_ng_table.scss · ng_table.js).
Component info
| Name | table-v2 |
|---|---|
| Type | css+js |
| Version | css 2.4.5 · js 2.5.0 |
| Status | stable |
| CSS | /assets/css/components/_ng_table.scss |
| JS | /assets/js/ng_table.js |
Classes
.ng-col-grow .ng-dropdown .ng-dropdown-item .ng-dropdown-menu .ng-filter .ng-filter-inline .ng-filter-label .ng-filter-select .ng-sort .ng-sort-indicator .ng-sortable .ng-table .ng-table-detail .ng-table-detail-content .ng-table-detail-inner .ng-table-empty .ng-table-error-box .ng-table-expander .ng-table-expander-cell .ng-table-expander-col .ng-table-expander-label .ng-table-expander-lg .ng-table-expander-md .ng-table-expander-sm .ng-table-expander-text .ng-table-expander-xl .ng-table-filters .ng-table-filters-left .ng-table-filters-right .ng-table-head-left .ng-table-head-right .ng-table-header .ng-table-subtitle .ng-table-table .ng-table-title .ng-table-wrapper .ng-tcol-center .ng-tcol-mono .ng-tcol-num .ng-tcol-trunc .ng-tcol-w- Tokens
--ng-table-border-color --ng-table-border-strong --ng-table-border-style --ng-table-border-width --ng-table-cell-padding --ng-table-check-bg --ng-table-check-border --ng-table-check-checked-bg --ng-table-check-checked-bg-border --ng-table-check-checked-color --ng-table-check-radius --ng-table-check-size --ng-table-detail-bg --ng-table-detail-pad --ng-table-focus --ng-table-gap --ng-table-head-bg --ng-table-head-height --ng-table-head-padding --ng-table-header-text --ng-table-line --ng-table-max-h --ng-table-muted --ng-table-row-even --ng-table-row-hover --ng-table-row-odd --ng-table-row-selected --ng-table-sticky-top --ng-table-text Events
ng:table:collapse ng:table:expand