Usage
Data Table renders tabular data from a TanStack Table column model and adds sorting, row selection, pagination and a declarative filter bar on top of Table. It is controlled and headless: you feed it columns, data and (optionally) state, and it emits a *-change event whenever the user interacts. It owns no data fetching, serialization or persistence — your app does that and feeds the next page of rows back in.
Use it when you have a list/collection screen and want sorting, filtering, selection and paging without re-implementing the table chrome each time. For a static or hand-authored table, use Table directly; for just the filter toolbar, see Data Table Filter Bar.
Import the component — this registers the custom element:
import "@nordhealth/components/lib/DataTable"
Then give it a caption (for accessibility), a column model and a page of rows. Everything is set as a JS property, not an attribute, because columns and data are objects/arrays:
The headless contract
Data Table never fetches, sorts, filters or paginates your data behind your back. The contract is one-way and explicit:
- You provide
columns, the current page ofdata, and (optionally) the controlledstate. - The user interacts — clicks a sort header, ticks a checkbox, changes a filter, moves a page.
- The table emits an event describing the new intent (
sorting-change,pagination-change, …) — it does not silently mutate anything you own. - Your app reacts — updates its query state and/or URL, fetches the matching rows, and assigns the new
data(andstate) back onto the element.
In client mode (the default) the table also keeps its own internal copy of any state slice you leave uncontrolled, and runs TanStack's client-side sort/filter/paginate row models over the data you gave it — handy for small, already-loaded datasets and demos. In server mode (manual) those client row models are skipped entirely: the table renders exactly the data you pass and relies on you to do the sorting, filtering and paging on the server in response to its events.
<!-- Server-driven: render exactly the rows we pass, emit intent only -->
<nord-data-table manual row-count="240"></nord-data-table>
Because the events carry intent (the new SortingState, the new PaginationState, …) rather than resolved rows, the same event handlers work whether you resolve them client-side against an in-memory array or turn them into an API request. That is what makes the component equally at home in a small settings screen and a server-paged, hundreds-of-thousands-of-rows list.
Composition
Data Table is a single custom element that assembles several Nord components into one collection view. You typically place it inside a Card so it sits on a padded surface with a heading. Internally it embeds the Data Table Filter Bar as a toolbar, a Table for the rows, and a Pagination control beneath — all wired to the same TanStack table instance so state stays in sync.
nord-card
└── nord-data-table
├── nord-data-table-filter-bar (toolbar — from `filters`)
├── nord-table (header + rows — from `columns` + `data`)
│ └── <table> / <thead> / <tbody> / <tfoot>
└── nord-pagination (paginator — from `pagination`)
<nord-card padding="none">
<h2 slot="header">Patients</h2>
<nord-data-table caption="Patients" id="patients"></nord-data-table>
</nord-card>
<script type="module">
import "@nordhealth/components/lib/DataTable"
const table = document.querySelector("#patients")
table.columns = [
{ accessorKey: "id", header: "ID", cell: info => info.getValue() },
{ accessorKey: "name", header: "Name", cell: info => info.getValue() },
{ accessorKey: "species", header: "Species", cell: info => info.getValue() },
]
table.data = [
{ id: 101, name: "Bella", species: "Dog" },
{ id: 102, name: "Milo", species: "Cat" },
]
</script>
Card
Card is the surrounding surface. It is not required, but it gives the table a heading, padding and a visual container. Use padding="none" so the table's own chrome (filter bar, rows, paginator) sits flush against the card edges, and put the screen title in the card's header slot.
Data Table
Data Table is the orchestrator. It builds and owns the TanStack table instance, renders the parts below, forwards state between them, and emits the *-change events. Almost all configuration is set as JS properties on this one element — columns, data, filters, pagination, state — and the sub-parts are generated from them.
Data Table Filter Bar
Data Table Filter Bar is the toolbar rendered above the rows from the filters descriptor list. Each descriptor maps to a Nord filter control (search, dropdown, date range, boolean, segmented, tag group, or a custom slot). It emits filters-change and filter-reset. Opt out with no-filter-bar and supply your own via the filter-bar slot, or add extra controls beside it with the toolbar-end slot.
Table
Table renders the actual header, body and footer from columns and data. Data Table forwards density, striped and hide-header straight through to it, and layers sorting affordances, selection checkboxes, sizing presets and sticky headers on top. Cell renderers in your column model compose other Nord components (badges, links, dropdowns) into the cells.
Pagination
Pagination is the paginator rendered beneath the rows from the pagination config. It emits pagination-change intent. Opt out with no-pagination and provide your own via the pagination slot, or drop it entirely in favour of infinite-scroll / manual-infinite-scroll.
Examples
Overview
The canonical table: accessor columns, a numeric preset, plain-string cells, a Nord badge cell and a display (no-accessor) actions column.
Cell renderers
Cells can return a string or any DOM node, so you can compose links, badges, formatted dates and an actions dropdown. Ready-made helpers are exported for framework code that can render Lit templates:
- Presentation:
textCell,badgeCell,linkCell,emailCell(mailto:),phoneCell(tel:),actionsCell. - Formatting (locale/currency injectable — never hard-coded):
numberCell,currencyCell,percentCell,dateCellall take consumer-suppliedIntloptions. - Server HTML:
htmlCellrenders a trusted HTML string, or setmeta: { html: true }on a column to render its value as HTML with nocellfunction (server-rendered / Django tables).
For sizing, set meta.cellType to a preset (text, number, date, id/smallId/longId, actions, selection, icon, wide).
Set meta.textAlign once on the column definition; the same alignment is applied to its header (including the sort control), body cells and footer.
Sorting
Mark columns sortable in the model and the headers become real <button>s with aria-sort. In client mode the table sorts its own row model; listen to sorting-change to mirror the sort into your URL or a server query.
Multi-sort
Shift-click additional headers to sort by more than one column at once. The sorting-change detail is the ordered SortingState array of every active sort.
Row selection
Add enable-row-selection for a select-all / per-row checkbox column. Selection is keyed by getRowId, and each change emits row-selection-change with the selection map.
Bulk actions
While rows are selected, a header-row takeover shows the selected count, a selection-options dropdown (select all on page / deselect all), and your bulk actions. Compose the actions by placing a <nord-data-table-bulk-actions> element inside the table with your action buttons; the table renders it in the takeover row. The toolbar stays pinned to the visible start edge while the table scrolls horizontally, so the actions remain reachable in wide tables.
<nord-data-table id="patients" enable-row-selection>
<nord-data-table-bulk-actions>
<nord-button size="s">Export</nord-button>
<nord-button size="s" variant="danger">Delete</nord-button>
</nord-data-table-bulk-actions>
</nord-data-table>
Single select
Use enable-row-single-selection for radio-style selection where picking a row replaces the previous one and no select-all control is shown.
Filtering
Provide a filters descriptor list to render the embedded filter bar. Changing a filter emits filters-change with the full value map so you can re-fetch the matching rows. The reset-filters button only appears once at least one filter holds a non-default value.
All filter types
The filter bar supports search, dropdown, date range, boolean, segmented, tag group and custom filters — one descriptor per control.
Client pagination
Pass a pagination config and, in client mode, the table paginates its own row model. Page changes emit pagination-change.
Server-side pagination
Pair manual with row-count (the total number of server-side rows) and resolve pagination-change into an API request, assigning the returned page back onto data.
Infinite scroll
Instead of a numbered paginator, use infinite-scroll (client) or manual-infinite-scroll (server) to advance/append rows as the body scrolls near the bottom. The table also emits reached-bottom.
Tree expansion
Provide getSubRows to render nested child rows with an expand/collapse control. Expansion state emits expanded-change.
Master / detail
Provide renderSubRow to render a full-width detail panel beneath an expanded row — useful for inline detail views without navigating away.
Custom rows
Column meta hints (bodyColspan, bodySkipByColspan) let a row span cells for grouped or summary presentations.
Column pinning
Pin columns to the start or end so they stay visible while the rest of the table scrolls horizontally. Pinned columns cast a shadow at the scroll boundary.
Column sizing
A column's width comes from its explicit size, then a meta.cellType preset (selection, expand, icon, actions, number, date, text, wide), then TanStack's computed size.
Column resizing
Add enable-column-resizing for drag-to-resize handles in the header. Set enableResizing: false on an individual column definition to keep that column fixed. Sizing changes emit column-sizing-change.
Columns menu
Add columns-menu for a built-in column-visibility menu in the toolbar; add enable-column-reorder for drag-to-reorder handles. These emit column-visibility-change and column-order-change.
Sticky header
Add sticky-header so the header row stays pinned while the body scrolls. Set sticky-header-offset to clear fixed app chrome.
Loading skeleton
While loading with no data yet, the table renders skeleton-rows placeholder rows instead of an empty overlay, so layout does not jump when data arrives.
Empty and no-results
The empty state differs by cause: no data at all uses empty-message (or the empty slot), while active filters returning nothing show a distinct "no results" state with a clear-filters affordance.
Row errors
Set rowErrors (keyed by row id) to render a danger banner beneath each affected row listing its validation messages — useful for inline-edit failures.
Column footers
Give any visible column a footer and the table renders a <tfoot> totals row — handy for sums and counts.
Clickable rows
Set isRowClickable to gate which rows are activatable. Clickable rows emit row-click on click, Enter or Space with the row id and original data.
Server-side complete
A complete server-driven collection view: manual mode with server sorting, filtering and pagination wired together through the events.
API reference
Properties
None of these are attributes — set them with element.columns = … (or .columns=${…} / :columns in a framework). TanStack Table types are re-exported from @nordhealth/components, so you can import them without adding @tanstack/table-core yourself:
import type {
ColumnDef,
SortingState,
PaginationState,
RowSelectionState,
ExpandedState,
Row,
TableState,
} from "@nordhealth/components"
import type { DataTablePaginationConfig, FilterDescriptor } from "@nordhealth/components"
| Property | Type | Description |
|---|---|---|
columns | ColumnDef<TData, any>[] | TanStack column definitions. Each cell returns a Lit template, string, or DOM node. |
data | TData[] | The current page of rows. In client mode the full dataset to sort/filter/paginate locally; in manual mode exactly the rows to display. |
state | Partial<TableState> | Controlled state (sorting, pagination, rowSelection, columnFilters, expanded, …). Any slice left out is tracked internally. |
filters | FilterDescriptor[] | Declarative config for the embedded filter bar. |
filterValues | FilterValues | Controlled value map for the embedded filter bar. |
pagination | DataTablePaginationConfig | Paginator config: total, pageSize, pageSizeOptions, type (numbers / simple). |
table | TanStackTable<TData> | Escape hatch: render a consumer-built TanStack Table instance verbatim. |
getRowId | (row, index, parent?) => string | Stable row id used by selection and expansion. |
getSubRows | (row) => TData[] | undefined | Enables tree expansion. |
getRowCanExpand | (row: Row<TData>) => boolean | Gates which rows can expand. |
renderSubRow | (row: Row<TData>) => TemplateResult | Full-width detail panel for an expanded row (master/detail). |
rowCount | number | Total row count for server-side pagination (attribute row-count). |
rowErrors | Record<string, string[]> | Per-row validation messages keyed by row id; each renders a danger banner. |
isRowClickable | (row: Row<TData>) => boolean | Gates which rows are clickable and emit row-click. |
rowGrouping | RowGroupingConfig | Group contiguous rows under collapsible group-header rows: { column, collapsible?, collapsedByDefault? }. Rows must arrive ordered by column. |
Columns can also opt into rendering their value as trusted HTML with meta: { html: true } — for server-rendered tables (e.g. Django) whose cells arrive as pre-rendered HTML strings, with no cell function and no framework import. See Cell renderers.
Attributes
| Attribute | Type | Description |
|---|---|---|
caption | string | Accessible caption (visually hidden, required). |
manual | boolean | Server-side mode: skip the client sort/filter/paginate row models. |
density | condensed / default / relaxed | Row density, forwarded to the inner Table. |
striped | boolean | Zebra striping. |
hide-header | boolean | Hide the column header row. |
loading | boolean | Show the loading overlay / skeleton. |
empty-message | string | Override the default empty-state message. |
sticky-header | boolean | Pin the header row while the body scrolls. |
sticky-header-offset | number / auto | Top offset (px) for the sticky header to clear fixed chrome. |
skeleton-rows | number | Placeholder rows rendered while loading with no data (default 5). |
no-filter-bar | boolean | Opt out of the embedded filter bar (use the filter-bar slot instead). |
no-pagination | boolean | Opt out of the embedded paginator (use the pagination slot instead). |
infinite-scroll | boolean | Client-side infinite scroll: auto-advance the page near the bottom. |
manual-infinite-scroll | boolean | Server-side infinite scroll: emit reached-bottom near the bottom. |
enable-row-selection | boolean | Add the multi-select select-all / per-row checkbox column. |
enable-row-single-selection | boolean | Radio-style single-row selection (no select-all). |
enable-select-all-matching | boolean | Offer a "Select all N matching" affordance when the page is fully selected and rowCount exceeds it; emits select-all-matching (intent only — you resolve the ids). |
enable-column-resizing | boolean | Drag-to-resize column handles in the header. |
columns-menu | boolean | Render the built-in column-visibility menu in the toolbar. |
enable-column-reorder | boolean | Add drag-to-reorder handles to the columns menu. |
Events
Every interaction emits an unprefixed event whose detail is the new value for that state slice. None of these change what the table renders by themselves — re-render by assigning new data/state in response.
| Event | detail | Fires when |
|---|---|---|
sorting-change | SortingState | A sortable header is toggled. |
pagination-change | PaginationState | The page or page size changes. |
column-filters-change | ColumnFiltersState | Column filters change. |
global-filter-change | unknown | The global filter value changes. |
row-selection-change | RowSelectionState | Rows are selected or deselected. |
select-all-matching | { total } | The user chose "Select all N matching" across pages (opt-in via enable-select-all-matching). Resolve the ids server-side and apply the selection. |
expanded-change | ExpandedState | Rows are expanded or collapsed. |
column-visibility-change | VisibilityState | Column visibility changes. |
column-order-change | ColumnOrderState | Column order changes. |
column-pinning-change | ColumnPinningState | Column pinning changes. |
column-sizing-change | ColumnSizingState | Column sizing changes. |
state-change | TableState | Any state slice changes (fully-controlled escape hatch). |
row-click | { id, row } | A clickable row is activated (click / Enter / Space). |
reached-bottom | — | The body is scrolled near the bottom (infinite scroll). |
The embedded filter bar additionally emits filters-change (with { values, changed }), filter-reset and filter-add. See Data Table Filter Bar for details.
Slots & override renderers
This element is light-DOM, so consumer content placed with slot="…" (or the dedicated <nord-data-table-bulk-actions> element) is captured and re-projected into the rendered table.
| Slot | Description |
|---|---|
bulk-actions (or <nord-data-table-bulk-actions>) | Actions shown in the header-row takeover while rows are selected. |
toolbar-end | Extra controls beside the embedded filter bar. |
filter-bar | Replaces the embedded filter bar (pair with no-filter-bar). |
pagination | Replaces the embedded paginator (pair with no-pagination). |
For the in-grid states, use the render-prop properties instead of slots:
| Property | Description |
|---|---|
renderEmpty | () => TemplateResult — custom empty state (replaces the default message + clear-filters action). |
renderLoading | () => TemplateResult — custom loading overlay (replaces the default spinner). |
renderSubRow | (row) => TemplateResult — master/detail content for an expanded row. |
Framework-owned rows
<nord-data-table> is the ready-made Lit renderer. In React or Vue applications that need native JSX, render functions or scoped slots for cells, let the framework own the semantic <table> markup instead. Use the framework's TanStack adapter (@tanstack/react-table or @tanstack/vue-table) with the same column metadata, sizing presets and localization exported by @nordhealth/components.
Load the reusable class stylesheet once, put .n-data-table on the nord-table shell, and use the n-data-table-* classes on sorting, resizing and state affordances:
import "@nordhealth/components/lib/Table"
import "@nordhealth/components/lib/data-table.css"
import {
applyColumnSizingDefaults,
CELL_TYPE_SIZES,
getColumnSize,
} from "@nordhealth/components"
Pass applyColumnSizingDefaults(columns) to the framework adapter before creating the table so meta.cellType presets are installed before TanStack resolves its 150px fallback.
<nord-table class="n-data-table">
<table><!-- React or Vue owns thead, tbody, rows and cells. --></table>
</nord-table>
This is the “own your rows on the core” pattern:
- Shared core: TanStack column definitions,
ColumnMeta, sizing presets, filter descriptors and localization. - React:
useReactTable+flexRender; cells are ordinary JSX/render props. - Vue:
useVueTable+FlexRender; cells are ordinary Vue templates, scoped slots or VNodes. - Lit or no-framework consumers:
<nord-data-table>remains the complete config-driven renderer.
The repository's React and Vue playgrounds contain side-by-side framework-owned and raw Lit examples. Framework renderers should consume these shared primitives rather than re-deriving column sizes or copying component CSS.
Renderer and application boundary
Every renderer stops at presentation and user intent. Application workflows stay in the consuming application:
| Responsibility | Owner |
|---|---|
| Render headers, rows, cells, loading/empty/error states | Renderer |
| Emit sorting, filtering, pagination and selection intent | Renderer |
| Data fetching and request cancellation | Application |
| API request/response mapping | Application |
| URL sync and navigation state | Application |
| User preference persistence and saved views | Application |
| CSV/PDF/print export and other domain actions | Application |
This boundary is the same for the Lit, React, Vue and plain-JavaScript renderers. A renderer emits the next state; the application updates its URL or saved state, performs fetching, and supplies the resulting rows.
Usage with plain JS (jQuery / Django)
Data Table is a standard custom element, so it works anywhere you can run JavaScript — no framework required. Render the empty element from your template, then set its properties from a script and listen for events to re-fetch. This pattern fits a Django template that ships JSON in a <script type="application/json"> tag, or a jQuery-driven page.
The Django adapter belongs in the application, not in the design-system package. Provet's legacy aoData parameters, iSortCol_N visual-to-data column remapping, { data, recordsTotal, recordsFiltered } responses, filter serialization and saved-filter endpoints are application contracts. Keep that adapter beside the Django API and translate its result into the renderer's .data and rowCount properties. The design system intentionally contains no Provet endpoint, persistence or legacy-wire knowledge.
<nord-data-table caption="Patients" id="patients" manual row-count="0"></nord-data-table>
<script id="patients-data" type="application/json">
{{ patients_json|safe }}
</script>
<script type="module">
import "@nordhealth/components/lib/DataTable"
const table = document.querySelector("#patients")
table.columns = [
{ accessorKey: "id", header: "ID", cell: info => info.getValue(), meta: { textAlign: "end", cellType: "number" } },
{ accessorKey: "name", header: "Name", cell: info => info.getValue() },
{ accessorKey: "species", header: "Species", cell: info => info.getValue() },
]
table.filters = [
{ type: "search", key: "q", label: "Search", placeholder: "Search patients" },
]
// Initial page rendered by Django.
const initial = JSON.parse(document.getElementById("patients-data").textContent)
table.data = initial.results
table.rowCount = initial.count
// Re-fetch whenever the user sorts, pages, or filters. A new intent aborts
// the previous request so a slow response cannot overwrite newer rows.
let pending
async function reload(params) {
pending?.abort()
pending = new AbortController()
const res = await fetch("/api/patients/?" + new URLSearchParams(params), {
signal: pending.signal,
})
const page = await res.json()
table.data = page.results
table.rowCount = page.count
}
let query = {}
table.addEventListener("sorting-change", e => {
const [sort] = e.detail
query = { ...query, ordering: sort ? (sort.desc ? "-" : "") + sort.id : "" }
reload(query)
})
table.addEventListener("pagination-change", e => {
query = { ...query, page: e.detail.pageIndex + 1, page_size: e.detail.pageSize }
reload(query)
})
table.addEventListener("filters-change", e => {
query = { ...query, ...e.detail.values, page: 1 }
reload(query)
})
</script>
With jQuery the only difference is how you reach the element and bind events — the property/event contract is identical:
const table = $("#patients")[0]
table.columns = columns
table.data = rows
$(table).on("sorting-change", e => {
reload({ ordering: toOrdering(e.originalEvent.detail) })
})
Because columns and data are object/array properties, always assign them with JS (table.columns = …) — they cannot be passed as HTML attributes.
Usage with Vue
In Vue, bind the object/array properties with .prop (or v-bind of a ref) and listen for the kebab-case events with @. Vue assigns non-string bindings as DOM properties automatically when the value is non-primitive, but .prop makes the intent explicit. The TanStack types come straight from @nordhealth/components.
<script setup lang="ts">
import { ref } from "vue"
import { badgeCell, type BadgeVariant } from "@nordhealth/components"
import type { ColumnDef, SortingState, PaginationState } from "@nordhealth/components"
import "@nordhealth/components/lib/DataTable"
interface Patient {
id: number
name: string
species: string
status: "active" | "inactive"
}
const variant: Record<Patient["status"], BadgeVariant> = {
active: "success",
inactive: "warning",
}
const columns: ColumnDef<Patient, any>[] = [
{ accessorKey: "name", header: "Name", cell: info => info.getValue() },
{ accessorKey: "species", header: "Species", cell: info => info.getValue() },
{
accessorKey: "status",
header: "Status",
cell: info => badgeCell(info.getValue(), variant[info.getValue() as Patient["status"]]),
},
]
const rows = ref<Patient[]>([])
const total = ref(0)
const state = ref({ sorting: [] as SortingState, pagination: { pageIndex: 0, pageSize: 20 } as PaginationState })
async function reload() {
const res = await fetch(`/api/patients/?${new URLSearchParams({
page: String(state.value.pagination.pageIndex + 1),
page_size: String(state.value.pagination.pageSize),
})}`)
const page = await res.json()
rows.value = page.results
total.value = page.count
}
function onSorting(e: CustomEvent<SortingState>) {
state.value = { ...state.value, sorting: e.detail }
reload()
}
function onPagination(e: CustomEvent<PaginationState>) {
state.value = { ...state.value, pagination: e.detail }
reload()
}
reload()
</script>
<template>
<nord-data-table
caption="Patients"
manual
:row-count="total"
.columns="columns"
.data="rows"
.state="state"
@sorting-change="onSorting"
@pagination-change="onPagination"
/>
</template>
If you use @vue/compiler-dom's custom-element handling (isCustomElement), the .columns / .data / .state bindings forward to DOM properties so the table gets real objects, not stringified attributes.
Accessibility
- Always set a
caption. It is rendered as a visually-hidden<caption>so screen-reader users hear what the table contains, while sighted users rely on the surrounding heading. - The host has
role="region", so screen-reader users can jump to the table as a landmark; give it a heading oraria-labelif a page has more than one. - Sortable headers are real
<button>s witharia-sortreflecting the current direction, so the sort state is announced. - The selection checkboxes have accessible names (localised "Select row" / "Select all rows"), and the select-all control reports an indeterminate state when only some rows on the page are selected.
- Clickable rows are keyboard-operable (Enter / Space) and expose the row action to assistive technology; only rows allowed by
isRowClickableare focusable and activatable. - The loading overlay uses a labelled Spinner; the empty and no-results states are plain, localisable text you can override via
empty-messageor theemptyslot. - All visible strings — sort labels, selection labels, expand/collapse, resize/reorder handles, pagination controls, and loading/empty text — are localised through the design system's localization, so set the document language and provide translations as usual.
API reference
DataTable
Data Table renders tabular data from a TanStack column model with sorting,
selection, pagination and filtering. It is **controlled and headless**: feed
it columns, data and state and react to its *-change events — it owns
no data fetching, serialization or persistence. Filters and pagination are
declared as config (filters, pagination) and rendered by the table.
Consumer content for the slots below is captured from light-DOM slot="…"
children and re-projected into the rendered table (this element is light-DOM,
so native <slot> projection isn't available).
<nord-data-table></nord-data-table>Props
| Property | Attribute | Description | Type | Default |
|---|---|---|---|---|
rowCount | row-count | Total row count for server-side pagination. | number | undefined | — |
manual | manual | Server-side mode: skip client sort/filter/paginate row models. | boolean | false |
caption | caption | Accessible caption (required). | string | '' |
density | density | Row density, forwarded to the inner nord-table. | 'condensed' | 'default' | 'relaxed' | 'default' |
striped | striped | Zebra striping. | boolean | false |
hideHeader | hide-header | Hide the column header row. | boolean | false |
loading | loading | Show the loading overlay. | boolean | false |
emptyMessage | empty-message | Override the default empty-state message. | string | undefined | — |
stickyHeader | sticky-header | Pin the header row to the top of the scroll container while the body scrolls. | boolean | false |
stickyHeaderOffset | sticky-header-offset | Top offset (px) for the sticky header. 'auto' keeps the header flush with
the scroll container; a number sets an explicit inset-block-start so the
header can clear a fixed app chrome. | number | 'auto' | 'auto' |
skeletonRows | skeleton-rows | Number of placeholder rows rendered while loading with no data yet. | number | 5 |
noFilterBar | no-filter-bar | Opt out of rendering the embedded filter bar. | boolean | false |
noPagination | no-pagination | Opt out of rendering the embedded paginator. | boolean | false |
infiniteScroll | infinite-scroll | Client-side infinite scroll: reveal the next page of rows automatically as
the body is scrolled near the bottom (also emits reached-bottom). Mutually
exclusive with a numbered paginator. | boolean | false |
manualInfiniteScroll | manual-infinite-scroll | Server-side infinite scroll: emit reached-bottom near the bottom so the
consumer can append the next page. Does not advance the page itself. | boolean | false |
enableRowSelection | enable-row-selection | Enable multi-select row checkboxes. | boolean | false |
enableRowSingleSelection | enable-row-single-selection | Enable single-select row radios instead of checkboxes: selecting a row replaces any previous selection (at most one row selected at a time) and no select-all control is rendered. Implies row selection. | boolean | false |
enableSelectAllMatching | enable-select-all-matching | Opt into a "select all N matching" affordance in the bulk-actions bar when
every row on the current page is selected but more rows match than are on the
page (rowCount exceeds the page). The component only emits
select-all-matching with the total; the consumer resolves the ids. | boolean | false |
enableColumnResizing | enable-column-resizing | Enable drag-to-resize column handles in the header (resize mode onChange). | boolean | false |
columnsMenu | columns-menu | Render the built-in column-visibility (and reorder) menu in the toolbar. | boolean | false |
enableColumnReorder | enable-column-reorder | Add drag-to-reorder handles to the columns menu (updates columnOrder). | boolean | false |
Slots
| Slot name | Description |
|---|---|
toolbar-end | Extra controls beside the embedded filter bar. |
bulk-actions | Actions shown in the header-row takeover while rows are selected. |
filter-bar | Replaces the embedded filter bar (pair with no-filter-bar). |
pagination | Replaces the embedded paginator (pair with no-pagination). |
| Event | Detail Type | Description |
|---|---|---|
sorting-change | NordDataTableSortingChangeEvent | Sorting changed. |
pagination-change | NordDataTablePaginationChangeEvent | Pagination changed. |
column-filters-change | NordDataTableColumnFiltersChangeEvent | Column filters changed. |
global-filter-change | NordDataTableGlobalFilterChangeEvent | The global (search) filter changed. |
filters-change | NordDataTableFiltersChangeEvent | The embedded filter bar committed a change (re-emitted from the table). |
filter-reset | NordDataTableFilterResetEvent | Filters were reset (via the filter bar or the empty-state clear action). |
row-selection-change | NordDataTableRowSelectionChangeEvent | Row selection changed. |
expanded-change | NordDataTableExpandedChangeEvent | Expanded rows changed. |
row-click | NordDataTableRowClickEvent | A clickable row was activated. |
reached-bottom | NordDataTableReachedBottomEvent | The table body was scrolled near the bottom (infinite scroll). |
Parts
This component is made up of the following parts.
Data Table Bulk Actions
Actions for a Data Table's bulk-selection state.
Place it as a direct child of nord-data-table and put your action buttons
inside; the table captures it and renders it in the header-row takeover that
appears while rows are selected (next to the "N selected" count).
It's a light-DOM marker (styled display: contents by the parent table) — it
hosts your controls without adding a box, so they sit inline in the takeover.
<nord-data-table-bulk-actions></nord-data-table-bulk-actions>Slots
| Slot name | Description |
|---|---|
Default slot | The bulk action controls (typically nord-buttons). |
Data Table Filter Bar
Data Table Filter Bar renders a row of filter controls from a declarative
filters config — the filter equivalent of a column model. Each descriptor
maps to the matching nord-filter-* control; the bar tracks the value map and
emits a single filters-change event. It owns no data fetching or
serialization. Used embedded by Data Table, or
standalone (bind to a table via for=).
<nord-data-table-filter-bar></nord-data-table-filter-bar>Props
| Property | Attribute | Description | Type | Default |
|---|---|---|---|---|
align | align | Cross-axis alignment, forwarded to nord-filter-bar. | 'start' | 'center' | 'end' | 'center' |
for | for | Id of the <nord-data-table> this bar controls when used standalone. | string | undefined | — |
collapseBreakpoint | collapse-breakpoint | Inline-size below which segmented and tag filters use dropdown presentation. | number | 640 |
| Event | Detail Type | Description |
|---|---|---|
search-change | CustomEvent | An async dropdown search changed; detail contains its filter key and value. |
load-more | CustomEvent | An async dropdown requested another options page; detail contains its filter key. |
filters-change | NordDataTableFiltersChangeEvent | A filter committed a change. |
filter-reset | NordDataTableFilterResetEvent | The reset affordance was used. |
filter-add | NordDataTableFilterAddEvent | An optional filter was added from the add-filter menu. |
Dependencies
This component is internally dependent on the following components:
- <nord-banner>
Banner
Banner informs users about important changes or conditions in the interface. Use this component if you need to communicate to users in a prominent way.
- <nord-button>
Button
Buttons are used for interface actions. Primary style should be used only once per section for main call-to-action, while other styles can appear more frequently.
- <nord-checkbox>
Checkbox
Checkboxes allow user to choose one or more options from a limited set of options. If you have more than 10 options, please use Select component instead.
- <nord-dropdown>
Dropdown
Dropdown menu displays a list of actions or selectable options for a user. Dropdown uses popout component internally to create the overlay functionality.
- <nord-empty-state>
EmptyState
Empty state can be used when there is no data to display to describe what the user can do next. Empty state provides explanation and guidance to help user progress.
- <nord-icon>
Icon
Icons are used to provide additional meaning or in places where text label doesn’t fit. Icon component allows you to display an icon from the Nordicons library.
- <nord-pagination>
Pagination
Pagination is the navigation root for a paginated collection. It is composition-first: it owns no page state, renders no controls, and emits no events. Your app owns the page state, URL/query syncing, cursors and any data fetching; Nord supplies the semantic/layout primitives, the styling and the paginate() math utility for building the page window. As a root it is a navigation landmark — it sets role="navigation" and a default accessible label so screen-reader users can jump to it. Compose the lower-level primitives inside it: - Pagination Content — the list row. - Pagination Item — a slot in the row. - Pagination Link — a page link (set current). - Pagination Previous — the previous control. - Pagination Next — the next control. - Pagination Ellipsis — the collapsed-pages marker. Bring your own interactive element — an <a> (e.g. a framework <Link> / <NuxtLink>) or a nord-button — and own its href, navigation and disabled state. See the framework adapters in the docs.
- <nord-radio>
Radio
Radio buttons are graphical user interface elements that allow user to choose only one option from a predefined set of mutually exclusive options.
- <nord-select>
Select
Select lets users choose one option from an options menu. Consider using select when you have 5 or more options to choose from.
- <nord-skeleton>
Skeleton
Skeletons are used to provide a low fidelity representation of content before it appears in a view. This improves the perceived loading time for our users.
- <nord-spinner>
Spinner
Spinner component is used to indicate users that their action is being processed. You can customize the size and color of the spinner with the provided properties.
- <nord-table>
Table
Table is used to organize and display information from a data set. Provides table styles in addition to features like sticky headers and support for narrow viewports.
- <nord-visually-hidden>
VisuallyHidden
Visually hidden is used when an element needs to be available to assistive technologies like screen readers, but be otherwise hidden.