Installation
import { WrTable, WrTableCell, type WrTableColumns } from 'ngwr/table';
@Component({ imports: [WrTable, WrTableCell] })
export class MyComponent {}
// Each projected template is its own directive, and `imports: []` takes the
// class, not the selector. Add the ones your template actually uses:
// <ng-template wrTableCell> -> WrTableCell
// <ng-template wrTableExpand> -> WrTableExpand
// <ng-template wrTableGroupHeader> -> WrTableGroupHeaderBasic usage
Define columns as a record keyed by row property names. Rows are plain objects.
<wr-table [columns]="columns" [items]="rows" />Sort + filter + custom cell
Sortable columns cycle asc → desc → off. Filter dropdown emits the selected items via filterChange. Project an [wrTableCell] template to customize a column's rendering.
<wr-table [columns]="columns" [items]="rows" [(sort)]="sort" (filterChange)="onFilter($event)">
<ng-template wrTableCell="role" let-value>
<wr-tag [color]="value === 'admin' ? 'danger' : 'medium'">{{ value }}</wr-tag>
</ng-template>
</wr-table>Sorting is state, not a sort
The header cycles asc → desc → off and writes the result into [(sort)]. That is all it does: the table never reorders items, in any mode — the same contract groupBy states about grouping. The rows in the demo above move because THIS page sorts them, and that comparator is the part a docs snippet usually leaves out, so it is spelled in full below. A table bound to a plain array with no comparator behind it shows the arrow flipping and the rows standing still, which reads as a broken control rather than as a contract.
// The table writes `sort` and reads nothing back from your rows. Sorting
// them is yours — this is the comparator the demo above runs.
protected readonly sort = signal<readonly WrTableSortState[]>([]);
protected readonly rows = computed(() => {
const rules = this.sort();
if (rules.length === 0) return this.source();
return [...this.source()].sort((a, b) => {
for (const { key, direction } of rules) { // array order = application order
if (!direction) continue; // a header cycled back to "off"
const cmp = String(a[key] ?? '').localeCompare(String(b[key] ?? ''));
if (cmp !== 0) return direction === 'asc' ? cmp : -cmp;
}
return 0;
});
});
// Server-side, the same array becomes query parameters — still no client sort.
effect(() => {
const [primary] = this.sort();
this.load({ sortBy: primary?.key, order: primary?.direction });
});sort is an array, not a single rule: each sortable header owns its own entry, a second header appends rather than replaces, and array order is application order — first entry is the primary key. A header cycled back to off drops its entry. That is why the comparator above loops instead of reading sort()[0].
The control is the sort button, not the whole cell: .wr-table__sort-btn, a real <button> named from sortLabel (or the table.sort catalog key). Clicking the title text or the header padding does nothing, so an end-to-end test drives the button — getByRole('button', { name: 'Sort column' }) — and reads the result off the header's aria-sort, which is ascending / descending / none on every sortable column and absent on the rest.
Sorting also never touches page. Clicking a header on page 3 leaves you on page 3, and in server-side mode that means the next request goes out with the new order and the old offset. Resetting to page 1 on a sort change is a policy, not a default — write it in the handler that reacts to the sort, the same place you build the query.
What a cell template can read
A [wrTableCell] template gets three context members, and only the value is implicit. The row itself is let-row="item" — a bare let-row binds the value a second time, which is the quiet way to get an undefined in a click handler.
<!-- The cell template's context is `WrTableCellContext`: three names,
and only the first is implicit.
let-value the cell value — item[columnKey]
let-row="item" the WHOLE row object
let-col="column" that column's own definition (title, width, …)
`let-row` alone binds `$implicit`, i.e. the value again — the row needs
the explicit `="item"`. -->
<ng-template wrTableCell="role" let-value let-row="item" let-col="column">
{{ col.title }}: {{ value }} — {{ row.email }}
</ng-template>Row actions
The column key is just a key — give the action column one nothing in the row answers to, leave its title empty, and read the row off the template context. Deleting here mutates the demo's own list; reload the section to get the rows back.
// A column whose cells are buttons: give it a key of its own and no
// title-bearing data behind it. `let-row="item"` is what the handler needs —
// the cell value for an action column is `undefined`, and that is fine.
const columns: WrTableColumns = {
name: { title: 'Name' },
email: { title: 'Email' },
actions: { title: '', width: 96 },
};
<wr-table [columns]="columns" [items]="rows">
<ng-template wrTableCell="actions" let-row="item">
<wr-btn size="sm" color="danger" outlined (click)="remove(row)">Delete</wr-btn>
</ng-template>
</wr-table>Loading, empty — and error
[loading] and emptyLabel are the two states the table draws itself. There is no error input, and that is deliberate: a failed request belongs to the page, not to the table. Render a <wr-alert> beside it and let the table show its empty state.
<!-- Two states are built in. `[loading]` draws the spinner overlay;
`items` that is empty (or null) draws the empty row, whose copy is
`emptyLabel` — or the `table.empty` i18n key when you leave it unset. -->
<wr-table [columns]="columns" [items]="rows()" [loading]="loading()" emptyLabel="No users yet" />
<!-- There is deliberately NO error input. A failed request is the page's
state, not the table's, so render it beside the table and pass the empty
list through: -->
@if (error(); as message) {
<wr-alert type="danger" title="Could not load users" [message]="message" />
}
<wr-table [columns]="columns" [items]="rows()" [loading]="loading()" />Pagination
Set [pageSize] for client-side pagination — the table slices rows automatically and renders a <wr-pagination> footer. Pass [total] to switch to server-side mode (you handle slicing).
<wr-table [columns]="columns" [items]="rows" [pageSize]="5" [(page)]="page" />Server-side paging — what to bind, what fires
Setting [total] switches the footer pager to server mode: the table stops slicing and renders whatever items you hand it. page is a model(), so the event it publishes is (pageChange) — that is the output to listen to, and there is no separately named one. Bind it either way: [(page)] when the page number is your own signal, or [page] + (pageChange) when the click has to go through a handler that builds the request.
<!-- `[total]` switches the pager to server mode; `[pageSize]` still has to
be there or no pager renders at all. `page` is a model, so the output
it publishes is `(pageChange)`. -->
<wr-table
[columns]="columns"
[items]="pageRows()"
[total]="total()"
[pageSize]="20"
[page]="page()"
(pageChange)="onPage($event)"
[(sort)]="sort"
/>// The handler. Nothing is sliced for you — you fetch the window you were asked for.
protected onPage(page: number): void {
this.page.set(page);
this.load(); // limit: 20, skip: (page - 1) * 20
}
// Two-way binding works just as well when the page number is your own state:
// <wr-table [total]="total()" [pageSize]="20" [(page)]="page" />
// …with an effect on `page` issuing the request.[pageSize] is still required in server mode. It no longer slices anything, but the footer renders only while pageSize > 0 and total exceeds it — a table given [total] alone shows no pager at all, which looks like the server contract being ignored. Pass the same number you send as the request's limit.
The footer is a plain <wr-pagination> with the page, size and total wired through — nothing else. It carries no showTotal range label and no page-size changer, and the table exposes no input to turn either on. When you need those, leave pageSize at 0 so the table renders no footer of its own and put your own <wr-pagination> underneath: it is the same component, with showTotal, showSizeChanger and [(pageSize)] available. That page also documents the two behaviours a server-paged host meets first — what happens while total is still 0 between requests, and who clamps the page after a size change.
Virtual scroll
Set virtualScroll with a fixed viewportHeight to window thousands of rows — only the visible slice is in the DOM. It forces fixed column layout, assumes a uniform rowHeight (auto-measured by default), and falls back to the full render while grouping, tree rows, expandable rows, responsive cards or a pager is active. The table below holds 10,000 rows.
<wr-table
virtualScroll
[rowHeight]="41"
[viewportHeight]="440"
[columns]="columns"
[items]="rows"
/>The number to pass. A body row is one line of text plus the cell padding and the row rule — 1.25rem line-height, 0.625rem of padding on each side multiplied by --wr-density-y, and 1px of border. At a 16px root that is 41px at md, and 32 / 48 / 55px at sm / lg / touch. Left at 0 the table measures the first rendered row and uses that, so it is right without being told; the value matters when you pass one, because the server has nothing to measure and falls back to a flat 40. Passing 40 for an md table is therefore off by a pixel per row — the prerendered window drifts a whole row every ~40 of them, until the client measurement replaces it. A taller custom cell template moves these numbers, and the table cannot know: measure once and pass what you measured.
Row selection
Set rowSelection to 'multiple' (checkboxes + a select-all header) or 'single'. Bind [(selection)] to the selected row keys, and rowKey to identify rows (a property name or a function).
<wr-table
rowSelection="multiple"
rowKey="email"
[(selection)]="selected"
[columns]="columns"
[items]="rows"
/>Expandable rows
Project an <ng-template wrTableExpand let-row> to reveal a detail panel per row — it adds a leading chevron column. Bind [(expanded)] (row keys) and set rowKey.
<wr-table rowKey="email" [(expanded)]="expanded" [columns]="columns" [items]="rows">
<ng-template wrTableExpand let-row>
<p>{{ row.name }} — {{ row.email }}</p>
</ng-template>
</wr-table>Summary row
Give a column a summary — a built-in aggregate ('sum' / 'avg' / 'count' / 'min' / 'max') over its numeric values, or a function of all rows. A footer row appears with the results (computed over the current items).
const columns: WrTableColumns = {
product: { title: 'Product', summary: () => 'Total' },
price: { title: 'Price', summary: 'avg' },
qty: { title: 'Qty', summary: 'sum' },
};Row grouping
Set groupBy to a row property (or a function) to gather rows under collapsible band headers. Grouping runs after pagination — sort by the same key, or use pageSize = 0, to keep each group whole. Bind [(collapsedGroups)] (keyed by group value) to control or persist collapse. Scroll a pinned table sideways and the group label stays pinned to the left edge like a frozen column.
<wr-table
groupBy="role"
[(collapsedGroups)]="collapsed"
[columns]="columns"
[items]="rows"
/>Group subtotals
Add groupSummary to repeat each column's summary aggregate as a subtotal row under every group, computed over that group's rows. The grand footer still totals the whole dataset — with client-side pagination the two describe different scopes, so pair groupSummary with pageSize = 0.
const columns: WrTableColumns = {
region: { title: 'Region', summary: () => 'Subtotal' },
rep: { title: 'Rep' },
deals: { title: 'Deals', summary: 'sum' },
revenue: { title: 'Revenue', summary: 'sum' },
};
<wr-table groupBy="region" groupSummary [columns]="columns" [items]="rows" />Custom group header
Project an <ng-template wrTableGroupHeader> for a rich band label — it receives the group value, label, rows, count, collapsed and a toggle() callback. The chevron and the group checkbox are still rendered by the table, so the template may contain its own interactive content.
<wr-table groupBy="role" [columns]="columns" [items]="rows">
<ng-template wrTableGroupHeader let-value let-count="count">
<wr-tag color="primary" transparent>{{ value }}</wr-tag>
<small>{{ count }} users</small>
</ng-template>
</wr-table>CSV export
Call exportCsv() on the table (via a template ref) to download its rows — headers from the column titles, values from the row data, in the current column order. It covers the current items, so in server-side mode (total set) that's the current page. { selectedOnly: true } exports just the selected rows; toCsv() returns the string instead of downloading.
<wr-table #table [columns]="columns" [items]="rows" />
<wr-btn (click)="table.exportCsv({ filename: 'users.csv' })">Export CSV</wr-btn>Pinned columns
Set pin: 'left' or pin: 'right' on a column to freeze it against that edge while the rest scrolls horizontally. Several per side stack in order, and offsets are measured so columns of any width line up. Scroll the table below sideways — Name stays left, Status stays right.
const columns: WrTableColumns = {
name: { title: 'Name', pin: 'left' },
email: { title: 'Email' },
role: { title: 'Role' },
// …more columns in between…
status: { title: 'Status', pin: 'right' },
};Resizable columns
Set resizable: true for a drag handle on the header's right edge. The first drag freezes the current widths and switches the table to fixed layout so resizing is exact in both directions; width sets an initial px width.
const columns: WrTableColumns = {
name: { title: 'Name', resizable: true },
email: { title: 'Email', resizable: true, width: 240 },
role: { title: 'Role', resizable: true },
};Reorderable columns
Set reorderable to drag column headers into a new order. Bind [(columnOrder)] (an array of keys) to control or persist the arrangement. Pinned columns stay anchored — they aren't draggable.
<wr-table reorderable [(columnOrder)]="order" [columns]="columns" [items]="rows" />Narrow container (stacked cards)
With responsive, each row collapses to a labelled card when the table's own box is too narrow for columns — a container query on its own width, so it adapts inside a split pane on any screen. Every cell shows its column title as a label. The box below is fixed at 360px.
<div style="width: 360px"><wr-table responsive [columns]="columns" [items]="rows" /></div>Tree rows
Set childrenKey to the property (or a function) holding each row's children and items becomes the roots. The forest is flattened into the same <tbody>, so child rows are ordinary <tr>s — column pin, resize, drag-reorder and [wrTableCell] templates all keep working at every depth. Open state reuses [(expanded)] and identity reuses rowKey. treeColumn picks which column carries the indent and chevron; without it the first rendered column does. pageSize pages the roots, selection and the summary row see every node, and the table announces itself as a treegrid with aria-level / aria-posinset / aria-setsize per row. Three pairings are refused rather than half-supported: groupBy wins over childrenKey, since a group buckets a flat list and a forest has none; a projected [wrTableExpand] loses to tree rows, which already own the row's disclosure; and virtualScroll falls back to the full render, because expanding a parent re-anchors the window.
<!-- `childrenKey` names the child array; `items` becomes the roots.
Open state reuses `[(expanded)]` and identity reuses `rowKey`. -->
<wr-table
rowKey="id"
childrenKey="reports"
treeColumn="name"
rowSelection="multiple"
[columns]="orgColumns"
[items]="org"
[(expanded)]="openRows"
[(selection)]="picked"
/>Types
Data shapes used by the inputs and outputs above.
type WrTableColumns = Record<string, WrTableColumn>;
interface WrTableColumn {
title: string;
sortable?: boolean;
filterItems?: readonly WrTableFilterItem[];
pin?: 'left' | 'right';
resizable?: boolean;
width?: number;
summary?: WrTableSummary;
}
interface WrTableFilterItem<T = unknown> {
title: string;
value: T;
selected?: boolean;
}
interface WrTableSortState {
key: string;
direction: 'asc' | 'desc' | null;
}
interface WrTableCsvOptions {
filename?: string;
selectedOnly?: boolean;
delimiter?: string;
escapeFormulas?: boolean;
}
interface WrTableGroupContext {
value: unknown;
label: string;
rows: readonly Record<string, unknown>[];
count: number;
collapsed: boolean;
index: number;
toggle: () => void;
}| Name | Description | Type | Default |
|---|---|---|---|
WrTableColumns | Column map — keys are row property names. | Record<string, WrTableColumn> | — |
WrTableColumn | A single column definition. | interface | — |
titlerequired | Heading shown in the header. | string | — |
sortable | Show a clickable sort indicator. | boolean | false |
filterItems | Non-empty list shows a filter dropdown. | readonly WrTableFilterItem[] | — |
pin | Freeze the column against the 'left' or 'right' edge while the rest scrolls. | 'left' | 'right' | — |
resizable | Add a drag handle on the header edge to resize. | boolean | false |
width | Initial column width in px (overridden by a drag). | number | — |
summary | Footer aggregate — 'sum' / 'avg' / 'count' / 'min' / 'max', or (rows) => value. | WrTableSummary | — |
WrTableFilterItem | One entry in a column filter. | interface | — |
titlerequired | Visible label. | string | — |
valuerequired | Value matched against the cell. | T | — |
selected | Pre-check the entry. | boolean | false |
WrTableSortState | Emitted by (sortChange). | { key: string; direction: WrTableSortDirection } | — |
WrTableCsvOptions | Argument of exportCsv() / toCsv(). | interface | — |
filename | Download filename. | string | 'table.csv' |
selectedOnly | Export only the selected rows (needs rowSelection). | boolean | false |
delimiter | Field delimiter — use ';' where Excel expects it. | string | ',' |
escapeFormulas | Prefix values starting with = + - @ so spreadsheets keep them as text. | boolean | true |
WrTableGroupContext | Passed to a wrTableGroupHeader template (also the built-in band context). | interface | — |
value | The value groupBy returned; also the collapse identity. | unknown | — |
label | Default label — String(value), or '—' for empty. | string | — |
rows | The group's rows on the current page. | readonly Record<string, unknown>[] | — |
count | rows.length — page-scoped row count. | number | — |
collapsed | Whether the group is currently collapsed. | boolean | — |
index | 0-based index of the group on the current page. | number | — |
toggle | Collapse / expand this group. | () => void | — |
API
| Name | Description | Type | Default |
|---|---|---|---|
columnsrequired | Column definitions, keyed by row property name. | WrTableColumns | — |
items | Row items. null/undefined renders the empty state; use [loading] for the spinner. Typed WrTableRow (= object) rather than Record<string, unknown>, so an array of an interface binds as readily as an array of a type — see {@link WrTableRow} for why those two are not the same to TypeScript. | readonly WrTableRow[] | null | undefined | null |
loading | Show the loading spinner overlay. | boolean | false |
responsive | Collapse each row to a labelled card when the table's own box is too narrow for columns (a container query on its own width, not the viewport). Every cell shows its column title as a label. | boolean | false |
reorderable | Enable drag-to-reorder on the column headers. | boolean | false |
columnOrder | Two-way bindable column order — an array of column keys. Reflects and drives the header order: the table falls back to declaration order for any key not listed, and updates this on drag. Bind it to persist a user's arrangement. | readonly string[] | [] |
rowSelection | Row selection with a leading checkbox column — 'multiple' adds a select-all header; 'single' keeps one row selected. | 'single' | 'multiple' | null | null (off) |
rowKey | How to identify a row for selection — a property name or a function. Unset uses the row object itself (fine for a stable row array). | string | ((row: Record<string, unknown>) => unknown) | null | null |
selection | Two-way bindable selected row keys. | readonly unknown[] | [] |
expanded | Two-way bindable expanded row keys (needs a [wrTableExpand] template). | readonly unknown[] | [] |
groupBy | Group rows under collapsible band headers — a row property name, or a function returning the group value. null (default) renders the table exactly as before. Grouping runs AFTER pagination: it buckets the rows on the current page in first-appearance order (it never re-sorts your data — same contract as [(sort)]), so a group straddling a page boundary shows a band on both pages, and per-group counts / subtotals are page-scoped. Sort items by the same key upstream to keep groups whole, or pair with pageSize = 0. Group values are compared by identity (Map/Set, SameValueZero) — return a primitive, exactly as for rowKey. Objects / Dates bucket by reference. | string | ((row: Record<string, unknown>) => unknown) | null | null |
childrenKey | Render the rows as a hierarchy: names the property (or computes the array) holding each row's children. items then means the ROOTS, and the forest is flattened depth-first into the same <tbody> — child rows are ordinary <tr>s going through the same cell loop, so column pin / resize / drag-reorder and [wrTableCell] templates keep working at every depth. Open state reuses the expanded model and row identity reuses rowKey, so a tree needs no second key space. Everything collapsed is the default. *Mutually exclusive with groupBy** — a forest has no flat list to bucket, so grouping wins and the hierarchy is ignored while it is set. Also mutually exclusive with [wrTableExpand] detail rows: both own the row's disclosure affordance. pageSize pages the ROOTS; a root brings its open descendants with it. The function form RETURNS WrTableRows, matching [items]: a return is covariant, so an array of interface-typed children satisfies it. Declared as records it did not, and a forest that bound to [items] could not hand its children back out. The PARAMETER stays Record<string, unknown> — that side is contravariant, and widening it would reject the callbacks that compile today while leaving row['reports'] unindexable. See {@link WrTableRow}. | string | ((row: Record<string, unknown>) => readonly WrTableRow[] | null | undefined) | null | null |
treeColumn | Which column carries the indent and the expand toggle. Keyed, not positional, so it survives a columnOrder drag. Defaults to whichever column renders first. | string | null | null |
toggleRowLabel | Accessible name of a parent row's expand toggle. Falls back to table.toggleRow. | string | null | null |
collapsedGroups | Two-way bindable collapsed group values (the values groupBy returns). Keyed by value, so a collapsed group stays collapsed across page changes and re-sorts. Empty (the default) shows every group expanded. | readonly unknown[] | [] |
groupSummary | Render an aggregate row under each group using the same column.summary definitions as the grand footer, computed over that group's rows on the current page. No-op unless at least one column defines a summary. The grand <tfoot> is unaffected and still aggregates the whole client dataset — with client-side pagination the two describe different scopes, so pair grouping with pageSize = 0 when you need them to reconcile. | boolean | false |
showGroupCount | Show the page-scoped row-count badge in each group band. | boolean | true |
sort | Two-way bindable sort array. Order in array = application order. | readonly WrTableSortState[] | [] |
emptyLabel | Text shown when there are no rows. Falls back to table.empty. | string | null | null |
sortLabel | Accessible name of a column's sort button. Falls back to table.sort. | string | null | null |
selectAllLabel | Accessible name of the select-all checkbox. Falls back to table.selectAll. | string | null | null |
selectRowLabel | Accessible name of a row's select checkbox. Falls back to table.selectRow. | string | null | null |
expandRowLabel | Accessible name of a row's expand toggle. Falls back to table.expandRow. | string | null | null |
selectGroupLabel | Accessible name of a group band's select checkbox. Falls back to table.selectGroup. | string | null | null |
toggleGroupLabel | Accessible name of a group band's collapse toggle. Falls back to table.toggleGroup. | string | null | null |
loadingLabel | Accessible name of the loading overlay. Falls back to table.loading. | string | null | null |
(filterChange) | Fires whenever a column's filter selection changes. | WrTableFilterChange | — |
pageSize | Rows per page. Set to 0 (default) to disable client-side pagination and render every row at once. | number | 0 |
page | Two-way bindable 1-based current page. | number | 1 |
total | Total row count for server-side pagination — when set, the table shows the pager but does NOT slice items (you provide the current page's slice yourself and react to (page) changes). Spelled total / page to match <wr-pagination>, which is the control this input renders. The two used to disagree about both words — totalItems / page here against total / currentPage there — which is a real cost for two components designed to sit on one screen. null rather than 0 is the difference that remains, and it carries meaning: it is the OFF switch for server mode, where the pager's own total is a count and 0 means an empty list. | number | null | null |
virtualScroll | Window the <tbody> so a table of thousands of rows keeps only ~one viewport of <tr>s in the DOM. Opt-in and OFF by default — a table without it renders byte-identically to today. Engages ONLY on the flat, fixed-height tier: it silently falls back to the full render whenever a variable-height layout is active — groupBy, a [wrTableExpand] template, responsive card mode, or a visible pager (pageSize > 0 / total). While on, it forces fixed layout for stable column widths and assumes a uniform row height (see rowHeight). Intended as static config. Give columns an explicit width so the frozen layout is exact (a virtualized table always renders fixed-layout); toggling it off at runtime leaves the frozen widths in place as column minimums. | boolean | false |
rowHeight | Uniform body-row height in px used to map scroll offset to row index. 0 (default) measures the first rendered row once — matching the density at that point — and reuses it; pass an explicit value to remove the post-hydration measure and make SSR pixel-exact (recommended if the density can change at runtime). Read only when virtualScroll is on. Keep virtualized cells single-line and no taller than this — a taller custom [wrTableCell] template drifts the window. | number | 0 |
viewportHeight | Height of the scroll viewport when virtualScroll is on — a number (px) or any CSS length ('70vh'). Applied as max-height on .wr-table__scroll (a short table shrinks to fit; a long one caps and scrolls). A numeric px value lets the server prerender the exact first window. | number | string | 480 |
overscan | Extra rows kept rendered above and below the viewport as scroll headroom. | number | 6 |
Templates
Projected templates the table renders in place of its defaults.
| Name | Description | Type | Default |
|---|---|---|---|
<ng-template wrTableExpand> | Detail template revealed when a row expands (let-row). | directive | — |
<ng-template wrTableGroupHeader> | Custom band label template (let-value, let-count, let-toggle, …). | directive | — |
[wrTableCell] | Per-column cell template. The attribute value is the column key it overrides; import WrTableCell. Its context is WrTableCellContext — three names, listed below. | directive | — |
let-value | The cell value, item[columnKey]. The implicit context member, so the name is yours to pick. | unknown | — |
let-row="item" | The whole row object. Needs the explicit ="item" — a bare let-row binds the implicit value instead. This is what a row-action button reads. | Record<string, unknown> | — |
let-col="column" | That column’s own definition — title, width, align, and the rest of WrTableColumn. | WrTableColumn | — |
Methods
Called on the component instance via a template reference.
| Name | Description | Type | Default |
|---|---|---|---|
exportCsv(options?) | Download the rows as a CSV file (options: filename, selectedOnly, delimiter). | (WrTableCsvOptions) => void | — |
toCsv(options?) | Return the table as a CSV string instead of downloading. | (WrTableCsvOptions) => string | — |
collapseAllGroups() / expandAllGroups() | Collapse or expand every group on the current page. | () => void | — |
scrollToRow(index, behavior?) | Scroll a row index to the top of the virtual viewport. | (number, ScrollBehavior) => void | — |
<wr-table-filter>
| Name | Description | Type | Default |
|---|---|---|---|
filterLabel | Accessible name of the filter trigger. Falls back to table.filter. | string | null | null |
noMatchesLabel | Text shown when the search finds nothing. Falls back to table.noMatches. | string | null | null |
searchLabel | Placeholder AND accessible name of the search box. Falls back to table.search. One string for both because the box has no visible label: the placeholder was its only name, so a hard-coded literal left the control unnamed in every other language rather than merely untranslated. | string | null | null |
resetLabel | Label of the clear-selection button. Falls back to table.reset. | string | null | null |
itemsrequired | — | readonly WrTableFilterItem[] | — |
(selectionChange) | Fires whenever the selection changes. | readonly WrTableFilterItem[] | — |
<wr-table-sort>
| Name | Description | Type | Default |
|---|---|---|---|
direction | — | WrTableSortDirection | null |
CSS variables
Custom properties ngwr/table publishes. Each default below is declared on the component's own selector, so a :root override is shadowed by it — set them on that selector, on a wrapper you scope yourself, or inline on the element. Unlike the BEM class names, these are the supported way to restyle the component.
| Variable | Default | Declared on |
|---|---|---|
--wr-table-bg | var(--wr-color-surface) | .wr-table |
--wr-table-border | var(--wr-color-outline) | .wr-table |
--wr-table-group-bg | var(--wr-color-fill-subtle) | .wr-table |
--wr-table-head-bg | var(--wr-color-fill) | .wr-table |
--wr-table-head-letter-spacing | var(--wr-tracking-normal) | .wr-table |
--wr-table-head-transform | none | .wr-table |
--wr-table-row-hover | var(--wr-color-fill-subtle) | .wr-table |