stable css 2.4.5 · js 2.5.0

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 Email 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
table.html
<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
table.html
<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 Email 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
table.html
<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
table.html
<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 Email 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
table.html
<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 Email 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
table.html
<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):

ID Name Email 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
table.html
<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 Email
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
table.html
<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:

Team members
Accounts and activation status
ID Name Email 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
table.html
<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
1Luca BrunoActive
2Marco RossiSuspended
3Sara NeriActive
table.html
<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:

app.js
// 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-sortInitial sort, "key:asc|desc".
data-table-limitRows per page; absent = no pagination.
data-table-page-sizeComma list for the rows-per-page select (e.g. 10,20,50).
data-table-page-size-positiontop · bottom · both (default).
data-table-pagination-alignPagination alignment (default left).
data-table-search-debounceSearch debounce in ms (default 0).
data-table-persistPersists the view state in localStorage (ng-table:<id> — give the root an id).
data-table-text-empty / -no-results / -errorLocalizable state texts.
data-table-sourceSwitches 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-expandableSub-table: expander column + caret per row (rows need data-row-id).
data-expand-singleOnly one detail row open at a time.
data-expand-icon / -collapse-iconPhosphor ph-* for the expander (default caret).
data-expand-text / -collapse-textText trigger instead of the icon.
data-expand-sizeExpander size: sm · md · lg · xl.

API

Each instance exposes root.__ngTable plus shortcuts directly on the root element:

app.js
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

Nametable-v2
Typecss+js
Versioncss 2.4.5 · js 2.5.0
Statusstable
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