Ga naar hoofdinhoud

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…

CnDataTable showing sortable columns, checkboxes, and row action buttons

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
RegionDescription
Select-all checkboxChecks/unchecks all rows on the current page
Column headersClickable to sort; cycles through ascending (▲), descending (▼), and no sort (indicator hidden)
Avatar / iconAuto-generated from the row's name field via CnCellRenderer
Cell valueType-aware rendering: email links, dates, booleans, status badges
Row actionsPer-row ⋮ menu — rendered via the #row-actions slot
Actions headerSlot above actions row — rendered via the #actions-header slot (only renders when row actions exist)
Loading overlaySpinner 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​

PropTypeDefaultDescription
schemaObjectnullSchema object for auto-generating columns from its properties map
columnsArray[]Column definitions: [{ key, label, sortable?, width?, align?, class?, cellClass?, formatter?, formatterOptions?, widget?, widgetProps?, aggregate? }]. formatter/widget/widgetProps resolve against the app's cnFormatters/cnCellWidgets registries (provided by CnAppRoot); formatterOptions is passed as the formatter's fourth argument (e.g. { currency: 'USD' } for the built-in currency formatter, or the { negative, zero, positive } phrases for conditionalPhrase) — see migrating-to-manifest → Column formatters / Column widgets. aggregate ({ register?, schema, op:"count", where }) renders the cell as a count of related OpenRegister objects (one _limit=0 request per row; @self.<path> in where interpolated per-row; … while loading, — on failure) — see migrating-to-manifest → Aggregate columns. The #column-{key} scoped slot still overrides everything.
rowIconString | FunctionnullOptional leading icon at the start of every row. A static MDI icon name (PascalCase, e.g. 'FileDocumentOutline') applied to all rows, or (row) => iconName to vary it per row. Resolved through the shared CnIcon registry. Unset = no icon column.
columnOverridesObject{}Per-column overrides applied on top of schema-generated columns; keyed by column key
excludeColumnsArray[]Column keys to hide when using schema auto-generation
includeColumnsArraynullWhitelist of column keys to show; all others hidden (takes precedence over excludeColumns)
rowsArray[]Array of row data objects to display
loadingBooleanfalseShows a loading spinner overlay while true
loadingTextString'Loading...'Accessible label for the loading spinner
sortKeyStringnullCurrently sorted column key; controls the ▲/▼ indicator. null means no column is actively sorted.
sortOrderString'asc'Current sort direction — 'asc', 'desc', or null (no sort)
sortKeysArray[]Ordered multi-column ("shift+click") sort key list, [{ key, order }, …] (0–3 entries). When non-empty it takes precedence over sortKey/sortOrder; a single-key list is single-sort's behavior unchanged. Shift+click a sortable header to append/cycle a secondary or tertiary key.
selectableBooleanfalseEnables the checkbox column for multi-row selection
rowClickToViewBooleanfalseWhen true, a row-body click emits row-click (for navigation) even while selectable — selection then happens only via the checkbox column ("click row = open, tick box = select")
selectedIdsArray[]Array of currently selected row IDs (controlled)
rowKeyString'id'Property name used as the unique row identifier
emptyTextString'No items found'Message shown when rows is empty and no #empty slot is provided
rowClassFunctionnullCallback (row) => cssClass to add dynamic CSS classes to rows
cellClassFunctionnullCallback (row, col) => cssClass to add dynamic CSS classes to individual data cells
scrollableBooleanfalseEnables horizontal scrolling for wide tables
selectAllLabelString'Select all rows'Accessible name (aria-label) for the select-all checkbox in the header row, so screen readers announce a named control (WCAG 4.1.2)
selectRowLabelString'Select row'Accessible name (aria-label) for each per-row select checkbox, so screen readers announce a named control (WCAG 4.1.2)
hideHeaderBooleanfalseHide the column-header row (<thead>). Useful for compact dashboard list widgets that want a plain bordered-row list without column labels.
fixedLayoutBooleanfalseSwitch to table-layout: fixed, making each column's width authoritative instead of a hint the browser may override from cell content. Opt in when content would otherwise dictate the layout: a long unbreakable value (a PHP FQCN, a UUID) widens its own column under the default auto layout and can paint past the cell box into its neighbour, while a column left unsized soaks up all remaining width. Cells break long words rather than overflowing. Columns with no width share what is left, so size every column when you want exact control — percentages summing to 100 are the easiest to reason about.
fillHeightBooleanfalseFill the parent's height (a flex-column card / widget content area) so an optional #footer is pushed to the bottom instead of floating under a short list; the footer stays pinned via its sticky rule when the list overflows. No-op outside a height-constrained parent — opt-in so ordinary in-flow tables are unaffected.

Events​

EventPayloadDescription
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.
selectids[]Emitted when row selection changes; payload is the full updated selection array
select-allisSelectAllEmitted when the select-all checkbox is toggled
row-clickrowEmitted when a data row is clicked (not the checkbox). Only fires when selectable is false — when selectable is true, a deliberate click anywhere on a row toggles its selection (emitting select) instead — a text-selection drag is not treated as a click.
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​

SlotScopeDescription
#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​

NameTypeRequiredDefaultDescription
columnsArray<{key: string, label: string, description: string, sortable: boolean, width: string, class: string, cellClass: string}&#124;string>[]Column definitions (manual mode). Not required when schema is provided. Each entry may be a full column object OR a bare string key; bare strings are normalised to { key, label } by effectiveColumns so manifest-driven pages that pass config.columns as a string array work without extra mapping. description renders as the header cell's tooltip and marks the label with a dotted underline, so a column whose meaning is not obvious from its name (a maturity level, a computed score, a domain term) can explain itself where the reader is looking. columnsFromSchema fills it from the JSON Schema property description automatically, so schema-driven tables get it for free.
rowIconstring &#124; ((row: object) => string) &#124; nullnullOptional leading icon shown at the start of every row. Either a static MDI icon name (PascalCase, e.g. 'FileDocumentOutline') applied to all rows, or a function (row) =&gt; iconName to vary it per row. The icon is resolved through the shared CnIcon registry. Unset = no icon column.
schemaobjectnullSchema object with properties field (schema-driven mode). When provided, columns are auto-generated from schema properties.
columnOverridesobject\{\}Per-column overrides when using schema mode: { key: { width, label, sortable, ... } }
excludeColumnsarray[]Column keys to exclude when using schema mode
includeColumnsarraynullColumn keys to include when using schema mode (whitelist)
rowsarray[]Row data array. Each row should have a unique identifier (see rowKey).
loadingbooleanfalseWhether data is loading (shows loading spinner)
sortKeystringnullCurrent sort column key
sortOrderstring'asc'Current sort order: 'asc', 'desc', or null (no sort)
sortKeysArray<{key: string, order: 'asc'&#124;'desc'}>[]Ordered multi-column sort state: [{ key, order }, ...] (priority order, 0 to 3 entries). Optional — when empty (the default), the table falls back to the legacy sortKey/sortOrder props, so single-sort hosts are completely unaffected. Shift+click a sortable header to append/cycle a secondary or tertiary key (see src/utils/multiColumnSort.js).
selectablebooleanfalseWhether rows can be selected with checkboxes
selectedIdsarray[]Array of currently selected row IDs
rowKeystring'id'Property name used as unique row identifier
emptyTextstring() =&gt; t('nextcloud-vue', 'No items found')Text shown when there are no rows
rowClassfuncnullFunction returning CSS class(es) for a row: (row) => string|object
cellClassfuncnullFunction returning CSS class(es) for a data cell: (row, col) => string|object
scrollablebooleanfalseWhether to constrain table height and make it scrollable
loadingTextstring() =&gt; t('nextcloud-vue', 'Loading...')Text shown while loading
selectAllLabelstring() =&gt; t('nextcloud-vue', 'Select all rows')Accessible name for the select-all checkbox in the header row. Used as the checkbox's aria-label so screen readers announce a named control (WCAG 4.1.2). Defaults to the lib's translation of "Select all rows".
selectRowLabelstring() =&gt; t('nextcloud-vue', 'Select row')Accessible name for a per-row select checkbox. Used as the checkbox's aria-label so screen readers announce a named control (WCAG 4.1.2). Defaults to the lib's translation of "Select row".
titlestring''Optional card title rendered in a header above the table. When set, the table reads as a self-contained card (the container's own border/radius is the card chrome). Folded in from the retired CnTableWidget.
borderlessbooleanfalseDrop the container's card chrome (border, radius, shadow) so the table sits flush inside a parent that already provides a card (e.g. a CnWidgetWrapper dashboard slot). Folded in from CnTableWidget.
fillHeightbooleanfalseFill the height of the parent (a flex-column card / widget content area) so the optional #footer is pushed to the bottom instead of floating directly under a short list. When the list is long enough to overflow, the footer stays pinned via its sticky rule. No-op outside a height-constrained parent. Opt-in so ordinary in-flow tables are unaffected.
hideHeaderbooleanfalseHide the column-header row (&lt;thead&gt;). Useful for compact dashboard list widgets that want a plain bordered-row list without column labels.
fixedLayoutbooleanfalseSwitch the table to table-layout: fixed, making each column's width authoritative instead of a hint the browser may override. Opt in when a column's content would otherwise dictate the layout — a long unbreakable value (a PHP FQCN, a UUID) widens its column under the default auto layout and can render past the cell box into its neighbour, while any column left unsized soaks up all remaining width. Cells also break long words rather than overflowing. Columns with no width share whatever space is left, so size every column when you want exact control.
limitnumber0Max number of rows to display. When the total exceeds it, only the first limit render and the "View all" footer appears (with viewAllRoute). 0 = show all. Folded in from CnTableWidget.
viewAllRouteunionnullvue-router route object for the "View all" footer link. The footer only shows when set AND the rows are a limit-ed subset. Folded in from CnTableWidget.
viewAllLabelstring() =&gt; t('nextcloud-vue', 'View all')Pre-translated "View all" footer label.
registerunionnullSelf-fetch mode (folded from CnTableWidget): the OpenRegister register id/slug. When register + schemaId are set and no rows are passed, the table fetches /apps/openregister/api/objects/{register}/{schemaId}.
schemaIdunionnullSelf-fetch mode (folded from CnTableWidget): the OpenRegister schema id used together with register. (Distinct from the schema prop, which is a JSON Schema object for column generation.)
fetchParamsunionnullExtra query parameters sent with the self-fetch request (register + schemaId mode) — e.g. a resolved filter map, _order[field] ordering, or _limit. Changing it re-triggers the self-fetch, so a host widget (CnWidgetObjectTable's declarative source) can drive filtering and ordering without re-implementing the fetch. Ignored when external rows are supplied.
rowClickRouteunionnullConvenience navigation (folded from CnTableWidget): a function that receives the clicked row and returns a vue-router route to push. When set, a row click navigates there (the row-click event still fires).
rowClickToViewbooleanfalseWhen true, a row-body click emits row-click (for navigation) even while selectable — selection then happens only via the checkbox column. Lets "click row = open, tick box = select" coexist. Default false keeps the legacy behaviour (selectable rows select on body click).

Events​

NamePayloadDescription
row-click—Emitted on a row-body click for navigation. Fires when selectable is false, OR when rowClickToView is set (selection then happens via the checkbox column).
row-context-menu—Emitted on a row right-click (contextmenu) for hosts that render a context menu.
select—Emitted when row selection changes. Payload: array of selected IDs.
select-all—Emitted when select-all checkbox is toggled.
sort—Emitted when a sortable column header is clicked (plain click) or shift-clicked (multi-sort).

Slots​

NameBindingsDescription
actions-header—Header cell content above the row-actions column (blank by default).
empty—Empty-state content shown when there are no rows (defaults to emptyText).
'column-' + col.keyname, row, valuePer-column cell override (#column-&lt;key&gt;), scoped with { row, value }. Wins over CnCellRenderer.
row-actionsrowPer-row actions menu (e.g. a CnRowActions), scoped with { row }. Supplying it adds the trailing actions column.
footertotal, shownCustom footer content, scoped with { total, shown } (defaults to the built-in "View all" link).

Card / widget mode (folded from CnTableWidget)​

CnDataTable is now the single table component — the deprecated CnTableWidget's features are folded in here as opt-in props (bare-table usage is unchanged):

  • title — render a card header (title + total-count badge) above the table.
  • borderless — drop the container's card chrome so the table sits flush inside a parent card (e.g. a CnWidgetWrapper dashboard slot).
  • limit — show only the first N rows; with viewAllRoute a "View all" footer appears.
  • viewAllRoute / viewAllLabel — the footer link's route and label.
  • register + schemaId — self-fetch rows from OpenRegister when no rows are passed.
  • fetchParams — extra query params for the self-fetch (a resolved filter map, _order[field] ordering, _limit); changing it re-triggers the fetch. Used by CnWidgetObjectTable's declarative source.
  • rowClickRoute — a function mapping a clicked row to a vue-router route to push.
  • hideHeader — drop the column-label row for a compact list widget.
  • #footer slot ({ total, shown }) — supply a custom footer link (e.g. "+ New" or an always-shown "View all") with its own handler; works outside a vue-router context.