CnDataTable
Sortable data table with row selection, loading states, and schema-driven column generation. Supports dot notation for nested values (e.g., address.city).
Wraps: NcLoadingIcon, NcCheckboxRadioSwitch, CnCellRenderer
Try it
Loading CnDataTable playground…

Anatomy
+--+------+----------↑---------+----------+----------+---------+----------------+
| | sel. | Column A ▲ | Column B | Column C | Column D| Actions header |
+--+------+--------------------+----------+----------+---------+----------------+
|☐ | 👤 | Alice van den Berg | Dept | email@.. | Active |⋮ |
|☐ | 👤 | Bob Jansen | Dept | email@.. | Pending |⋮ |
|☐ | 👤 | Carol Smit | Dept | email@.. | Active |⋮ |
+--+------+--------------------+----------+----------+---------+----------------+
↑ ↑ ↑ ↑ ↑
| avatar cell value badge row actions
checkbox renderer
| Region | Description |
|---|---|
| Select-all checkbox | Checks/unchecks all rows on the current page |
| Column headers | Clickable to sort; cycles through ascending (▲), descending (▼), and no sort (indicator hidden) |
| Avatar / icon | Auto-generated from the row's name field via CnCellRenderer |
| Cell value | Type-aware rendering: email links, dates, booleans, status badges |
| Row actions | Per-row ⋮ menu — rendered via the #row-actions slot |
| Actions header | Slot above actions row — rendered via the #actions-header slot (only renders when row actions exist) |
| Loading overlay | Spinner centered over the table body while loading is true |
| Empty state | "No items found" message (or #empty slot) when rows array is empty |
Usage
<CnDataTable
:schema="schema"
:rows="objects"
:sort-key="sortKey"
:sort-order="sortOrder"
:selectable="true"
:selected-ids="selected"
@sort="onSort"
@select="onSelect"
@row-click="onRowClick">
<template #row-actions="{ row }">
<CnRowActions :actions="rowActions" :row="row" @action="onAction" />
</template>
</CnDataTable>
Props
| Prop | Type | Default | Description |
|---|---|---|---|
schema | Object | null | Schema object for auto-generating columns from its properties map |
columns | Array | [] | Manual column definitions when not using schema: [{ key, label, sortable?, width?, class?, cellClass? }] |
columnOverrides | Object | {} | Per-column overrides applied on top of schema-generated columns; keyed by column key |
excludeColumns | Array | [] | Column keys to hide when using schema auto-generation |
includeColumns | Array | null | Whitelist of column keys to show; all others hidden (takes precedence over excludeColumns) |
rows | Array | [] | Array of row data objects to display |
loading | Boolean | false | Shows a loading spinner overlay while true |
loadingText | String | 'Loading...' | Accessible label for the loading spinner |
sortKey | String | null | Currently sorted column key; controls the ▲/▼ indicator. null means no column is actively sorted. |
sortOrder | String | 'asc' | Current sort direction — 'asc', 'desc', or null (no sort) |
selectable | Boolean | false | Enables the checkbox column for multi-row selection |
selectedIds | Array | [] | Array of currently selected row IDs (controlled) |
rowKey | String | 'id' | Property name used as the unique row identifier |
emptyText | String | 'No items found' | Message shown when rows is empty and no #empty slot is provided |
rowClass | Function | null | Callback (row) => cssClass to add dynamic CSS classes to rows |
cellClass | Function | null | Callback (row, col) => cssClass to add dynamic CSS classes to individual data cells |
scrollable | Boolean | false | Enables horizontal scrolling for wide tables |
Events
| Event | Payload | Description |
|---|---|---|
sort | { key, order } | Emitted when a sortable column header is clicked. Cycles through asc → desc → null. When the user clears the sort, both key and order are null. |
select | ids[] | Emitted when row selection changes; payload is the full updated selection array |
select-all | isSelectAll | Emitted when the select-all checkbox is toggled |
row-click | row | Emitted when a data row is clicked (not the checkbox) |
row-context-menu | { row, event } | Emitted when a data row is right-clicked. The native contextmenu event is prevented. Used by CnIndexPage with the useContextMenu composable to show a context menu at the cursor position. |
Slots
| Slot | Scope | Description |
|---|---|---|
#column-{key} | { row, value } | Override the cell renderer for a specific column key |
#row-actions | { row } | Content for the last (actions) cell of each row — typically CnRowActions |
#actions-header | - | Content for the header above the actions cell — typically a button |
#empty | — | Custom empty-state content shown when rows is empty |
Reference (auto-generated)
The tables below are generated from the SFC source via vue-docgen-cli. They reflect what's actually in CnDataTable.vue and update automatically whenever the component changes.
Props
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
columns | Array<{key: string, label: string, sortable: boolean, width: string, class: string, cellClass: string}> | [] | Column definitions (manual mode). Not required when schema is provided. | |
schema | object | null | Schema object with properties field (schema-driven mode). When provided, columns are auto-generated from schema properties. | |
columnOverrides | object | \{\} | Per-column overrides when using schema mode: { key: { width, label, sortable, ... } } | |
excludeColumns | array | [] | Column keys to exclude when using schema mode | |
includeColumns | array | null | Column keys to include when using schema mode (whitelist) | |
rows | array | [] | Row data array. Each row should have a unique identifier (see rowKey). | |
loading | boolean | false | Whether data is loading (shows loading spinner) | |
sortKey | string | null | Current sort column key | |
sortOrder | string | 'asc' | Current sort order: 'asc', 'desc', or null (no sort) | |
selectable | boolean | false | Whether rows can be selected with checkboxes | |
selectedIds | array | [] | Array of currently selected row IDs | |
rowKey | string | 'id' | Property name used as unique row identifier | |
emptyText | string | () => t('nextcloud-vue', 'No items found') | Text shown when there are no rows | |
rowClass | func | null | Function returning CSS class(es) for a row: (row) => string|object | |
cellClass | func | null | Function returning CSS class(es) for a data cell: (row, col) => string|object | |
scrollable | boolean | false | Whether to constrain table height and make it scrollable | |
loadingText | string | () => t('nextcloud-vue', 'Loading...') | Text shown while loading |
Events
| Name | Payload | Description |
|---|---|---|
row-click | — | |
row-context-menu | — | |
sort | — | Emitted when a sortable column header is clicked. |
select | — | Emitted when row selection changes. Payload: array of selected IDs. |
select-all | — | Emitted when select-all checkbox is toggled. |
Slots
| Name | Bindings | Description |
|---|---|---|
actions-header | — | |
empty | — | |
'column-' + col.key | name, row, value | |
row-actions | row |