n-data-table

A powerful data table component for displaying structured tabular data with features like sorting, filtering, pagination, selection, tree data, virtual scrolling, and more.

SKILL.md

GitHub

n-data-table Component

The n-data-table component is Naive UI's comprehensive solution for displaying structured tabular data. It provides extensive features for data grids, tables, and tabular data display.

When to Use

Use n-data-table when you need to:

  • Display structured data in rows and columns
  • Create data grids with sorting, filtering, and pagination
  • Show tabular data with selection capabilities
  • Render tree-structured hierarchical data
  • Handle large datasets with virtual scrolling
  • Build editable data tables
  • Export data to CSV format

Basic Usage

Simple Data Table

<template>
  <n-data-table
    :columns="columns"
    :data="data"
    :pagination="pagination"
    :bordered="false"
  />
</template>

<script setup>
import { ref } from 'vue'

const columns = [
  { title: 'Name', key: 'name' },
  { title: 'Age', key: 'age' },
  { title: 'Address', key: 'address' }
]

const data = ref([
  { name: 'John Brown', age: 32, address: 'New York No. 1 Lake Park' },
  { name: 'Jim Green', age: 42, address: 'London No. 1 Lake Park' },
  { name: 'Joe Black', age: 38, address: 'Sidney No. 1 Lake Park' }
])

const pagination = ref({
  pageSize: 10
})
</script>

Data Table with Selection

<template>
  <n-data-table
    :columns="columns"
    :data="data"
    :row-key="rowKey"
    @update:checked-row-keys="handleCheck"
  />
</template>

<script setup>
import { ref } from 'vue'

const rowKey = (row) => row.id

const columns = [
  { type: 'selection' },
  { title: 'Name', key: 'name' },
  { title: 'Age', key: 'age' }
]

const data = ref([
  { id: 1, name: 'John Brown', age: 32 },
  { id: 2, name: 'Jim Green', age: 42 }
])

const handleCheck = (keys) => {
  console.log('Selected rows:', keys)
}
</script>

Async Data Table

<template>
  <n-data-table
    remote
    :columns="columns"
    :data="data"
    :loading="loading"
    :pagination="pagination"
    @update:page="handlePageChange"
    @update:sorter="handleSorterChange"
    @update:filters="handleFiltersChange"
  />
</template>

<script setup>
import { ref, reactive } from 'vue'

const loading = ref(false)
const data = ref([])

const pagination = reactive({
  page: 1,
  pageSize: 10,
  itemCount: 0
})

const handlePageChange = async (page) => {
  loading.value = true
  pagination.page = page
  await fetchData()
  loading.value = false
}

const fetchData = async () => {
  // Fetch data from server
}
</script>

API Reference

Props

Name Type Default Description
allow-checking-not-loaded boolean false Whether to allow cascade checking on not loaded nodes in tree data
bordered boolean true Whether to show border
bottom-bordered boolean true Whether to show bottom border
cascade boolean true Whether to do cascade checking when using tree data
checked-row-keys Array<string | number> undefined The keys of checked rows (controlled)
children-key string 'children' The key of children data in tree data's data entity
columns Array<DataTableColumn> [] Columns to display
data Array<object> [] Data to display
default-checked-row-keys Array<string | number> [] Default checked row keys (uncontrolled)
default-expand-all boolean false Whether to expand all expandable rows
default-expanded-row-keys Array<string | number> [] Default expanded row keys (uncontrolled)
expanded-row-keys Array<string | number> undefined Expanded row keys (controlled)
filter-icon-popover-props PopoverProps { trigger: click, placement: bottom } Filter icon's Popover attributes
flex-height boolean false Whether to make table body height auto fit table area
get-csv-cell (value, row, col) => string undefined Custom function to get CSV cell content
get-csv-header (cols) => string undefined Custom function to get CSV header content
header-height number 28 Header height when virtual-scroll-header is enabled
height-for-row (rowData, index) => number undefined Height configuration function for each row
indent number 16 Indent of row content when using tree data
loading boolean false Whether to display loading status
max-height number | string undefined Max height of the table content
min-height number | string undefined Min height of the table content
min-row-height number 28 Min row height when virtual-scroll is enabled
paginate-single-page boolean true Whether to show pagination when data is less than one page
pagination false | object false Pagination configuration or false to disable
pagination-behavior-on-filter 'first' | 'current' 'current' Pagination behavior after filter state changes
remote boolean false Whether to use remote data (disables automatic paging)
render-cell (value, rowData, column) => VNodeChild undefined Custom render function for all cells
render-expand-icon ({ expanded, rowData }) => VNodeChild undefined Custom render function for expand icon
row-class-name string | (rowData, rowIndex) => string undefined Class name for each row
row-key (rowData) => number | string undefined Function to generate unique row key
row-props (rowData, rowIndex) => HTMLAttributes undefined Customize row HTML attributes
scroll-x number | string undefined Horizontal scroll width (required for fixed columns)
scrollbar-props ScrollbarProps undefined Scrollbar component props
single-column boolean false Whether rows are not divided (no border-bottom)
single-line boolean true Whether columns are not divided (no border-right)
size 'small' | 'medium' | 'large' 'medium' Table size
spin-props object undefined Loading spin props
sticky-expanded-rows boolean false Whether expanded row content remains sticky
striped boolean false Whether to show zebra stripes on rows
summary DataTableCreateSummary undefined Summary row data configuration
summary-placement 'top' | 'bottom' 'bottom' Summary rows placement
table-layout 'auto' | 'fixed' 'auto' CSS table-layout property
virtual-scroll boolean false Whether to use virtual scrolling for large data
virtual-scroll-header boolean false Whether to use virtual scrolling in header
virtual-scroll-x boolean false Whether to use horizontal virtual scrolling

Column Properties

Name Type Default Description
align 'left' | 'right' | 'center' 'left' Text alignment in column
allowExport boolean true Whether the column can be exported
cellProps (rowData, rowIndex) => object undefined HTML attributes for the cell
children DataTableColumn[] undefined Child columns for grouped header
className string undefined Class name for the column
colSpan (rowData, rowIndex) => number undefined Column span of the cell
customNextSortOrder (order) => order undefined Custom next sorting status function
defaultFilterOptionValue string | number | null null Default filter value (uncontrolled, single)
defaultFilterOptionValues Array<string | number> [] Default filter values (uncontrolled, multiple)
defaultSortOrder 'descend' | 'ascend' | false false Default sort order (uncontrolled)
disabled (rowData) => boolean () => false Whether the row is checkable
ellipsis boolean | EllipsisProps false Ellipsis options for overflow content
ellipsis-component 'ellipsis' | 'performant-ellipsis' 'ellipsis' Component for rendering text ellipsis
expandable (rowData) => boolean undefined Whether the row is expandable
filter boolean | function false Filter function or true for async filter
filterMode 'and' | 'or' 'or' Filter mode for multiple values
filterMultiple boolean true Whether multiple filter values allowed
filterOptionValue string | number | null undefined Active filter value (controlled, single)
filterOptionValues Array<string | number> | null undefined Active filter values (controlled, multiple)
filterOptions Array<{ label, value }> undefined Filter options
fixed 'left' | 'right' | false false Whether the column is fixed
key string | number undefined Unique key of the column
maxWidth number | string undefined Max width (when resizable is true)
minWidth number | string undefined Min width of the column
multiple boolean true Whether multiple selection mode
options Array undefined Custom selection options
render (rowData, rowIndex) => VNodeChild undefined Custom cell render function
renderExpand (rowData, rowIndex) => VNodeChild undefined Render function for expand area
renderFilter (options) => VNodeChild undefined Custom filter trigger render
renderFilterIcon (options) => VNodeChild undefined Custom filter icon render
renderFilterMenu (actions) => VNodeChild undefined Custom filter menu render
renderSorter (options) => VNodeChild undefined Custom sorter trigger render
renderSorterIcon (options) => VNodeChild undefined Custom sorter icon render
resizable boolean undefined Whether column width is resizable
rowSpan (rowData, rowIndex) => number undefined Row span of the cell
sortOrder 'descend' | 'ascend' | false undefined Sort order (controlled)
sorter boolean | function | 'default' false Sorter function or 'default' for built-in
title string | (() => VNodeChild) undefined Column title
titleAlign 'left' | 'right' | 'center' null Header text alignment
titleColSpan number undefined Column span for the title
tree boolean false Whether to show tree expand trigger
type 'selection' | 'expand' undefined Column type
width number | string undefined Width of the column

Events

Name Parameters Description
update:checked-row-keys (keys, rows, meta) Emitted when checked row keys change
update:expanded-row-keys (keys) Emitted when expanded row keys change
update:filters (filters, initiatorColumn) Emitted when filters change
update:page (page) Emitted when page changes
update:page-size (pageSize) Emitted when page size changes
update:sorter (sortState) Emitted when sorter changes

Slots

Name Parameters Description
empty - Custom content when data is empty
loading - Custom content when loading

Methods

Name Type Description
clearFilters () => void Clear all filter state
clearSorter () => void Clear all sort state
downloadCsv (options?) => void Download data as CSV file
filters (filters) => void Set active filters
page (page) => void Manually set the page
scrollTo (options) => void Scroll to specific position
sort (columnKey, order) => void Set sort state

Common Patterns

Pagination

<template>
  <n-data-table
    :columns="columns"
    :data="data"
    :pagination="pagination"
  />
</template>

<script setup>
import { ref, reactive } from 'vue'

const pagination = reactive({
  page: 1,
  pageSize: 10,
  showSizePicker: true,
  pageSizes: [10, 20, 50],
  onChange: (page) => {
    pagination.page = page
  },
  onUpdatePageSize: (pageSize) => {
    pagination.pageSize = pageSize
    pagination.page = 1
  }
})
</script>

Sorting

<template>
  <n-data-table
    :columns="columns"
    :data="data"
    @update:sorter="handleSorterChange"
  />
</template>

<script setup>
const columns = [
  {
    title: 'Name',
    key: 'name',
    sorter: (row1, row2) => row1.name.localeCompare(row2.name)
  },
  {
    title: 'Age',
    key: 'age',
    sorter: (row1, row2) => row1.age - row2.age
  }
]

const handleSorterChange = (sortState) => {
  console.log('Sort state:', sortState)
}
</script>

Filtering

<template>
  <n-data-table
    :columns="columns"
    :data="data"
    @update:filters="handleFiltersChange"
  />
</template>

<script setup>
const columns = [
  {
    title: 'Name',
    key: 'name'
  },
  {
    title: 'Address',
    key: 'address',
    filterOptions: [
      { label: 'London', value: 'London' },
      { label: 'New York', value: 'New York' }
    ],
    filter: (value, row) => row.address.includes(value)
  }
]

const handleFiltersChange = (filters) => {
  console.log('Filters:', filters)
}
</script>

Row Selection

<template>
  <n-data-table
    v-model:checked-row-keys="checkedRowKeys"
    :columns="columns"
    :data="data"
    :row-key="rowKey"
  />
</template>

<script setup>
import { ref } from 'vue'

const checkedRowKeys = ref([])

const rowKey = (row) => row.id

const columns = [
  { type: 'selection' },
  { title: 'Name', key: 'name' },
  { title: 'Age', key: 'age' }
]
</script>

Tree Data

<template>
  <n-data-table
    :columns="columns"
    :data="data"
    :row-key="rowKey"
    default-expand-all
  />
</template>

<script setup>
const columns = [
  { title: 'Name', key: 'name', tree: true },
  { title: 'Age', key: 'age' }
]

const data = [
  {
    id: 1,
    name: 'Parent',
    age: 40,
    children: [
      { id: 2, name: 'Child 1', age: 20 },
      { id: 3, name: 'Child 2', age: 18 }
    ]
  }
]

const rowKey = (row) => row.id
</script>

Virtual Scrolling for Large Data

<template>
  <n-data-table
    :columns="columns"
    :data="largeData"
    :max-height="400"
    virtual-scroll
  />
</template>

<script setup>
const largeData = Array.from({ length: 10000 }, (_, i) => ({
  id: i + 1,
  name: `User ${i + 1}`,
  age: Math.floor(Math.random() * 50) + 18
}))
</script>

Fixed Columns

<template>
  <n-data-table
    :columns="columns"
    :data="data"
    :scroll-x="1200"
    :max-height="300"
  />
</template>

<script setup>
const columns = [
  { title: 'ID', key: 'id', fixed: 'left', width: 100 },
  { title: 'Name', key: 'name', width: 200 },
  { title: 'Age', key: 'age', width: 100 },
  { title: 'Address', key: 'address', width: 300 },
  { title: 'Actions', key: 'actions', fixed: 'right', width: 150 }
]
</script>

Expandable Rows

<template>
  <n-data-table
    :columns="columns"
    :data="data"
  />
</template>

<script setup>
import { h } from 'vue'

const columns = [
  {
    type: 'expand',
    expandable: (row) => row.children?.length > 0,
    renderExpand: (row) => {
      return h('div', { style: 'padding: 16px;' }, 
        `Expanded content for ${row.name}`
      )
    }
  },
  { title: 'Name', key: 'name' },
  { title: 'Age', key: 'age' }
]
</script>

Export CSV

<template>
  <n-space vertical>
    <n-button @click="exportCsv">Export CSV</n-button>
    <n-data-table
      ref="tableRef"
      :columns="columns"
      :data="data"
    />
  </n-space>
</template>

<script setup>
import { ref } from 'vue'

const tableRef = ref(null)

const exportCsv = () => {
  tableRef.value?.downloadCsv({ fileName: 'data-export.csv' })
}
</script>

Best Practices

1. Always Define Row Keys

Each row should have a unique key for proper rendering and selection:

<n-data-table
  :row-key="(row) => row.id"
  :columns="columns"
  :data="data"
/>

2. Use Controlled Mode for Async Operations

When fetching data from a server, use controlled mode:

<n-data-table
  remote
  :loading="loading"
  :data="serverData"
  :pagination="{
    page: currentPage,
    pageSize: pageSize,
    itemCount: totalItems
  }"
  @update:page="handlePageChange"
/>

3. Optimize Large Datasets with Virtual Scrolling

For datasets with thousands of rows, enable virtual scrolling:

<n-data-table
  :data="largeData"
  :max-height="400"
  virtual-scroll
/>

4. Set Column Widths for Fixed Columns

When using fixed columns, always set explicit widths:

const columns = [
  { title: 'ID', key: 'id', fixed: 'left', width: 80 },
  // ... other columns
]

5. Use Pagination for Better Performance

Avoid rendering all data at once:

const pagination = {
  pageSize: 20,
  showSizePicker: true,
  pageSizes: [10, 20, 50, 100]
}

6. Handle Empty States

Provide meaningful empty state feedback:

<n-data-table :columns="columns" :data="data">
  <template #empty>
    <n-empty description="No data available" />
  </template>
</n-data-table>

7. Use Custom Render Functions for Complex Cells

For complex cell content, use render functions:

const columns = [
  {
    title: 'Status',
    key: 'status',
    render: (row) => {
      return h(NTag, { type: row.status === 'active' ? 'success' : 'warning' }, 
        () => row.status
      )
    }
  }
]

8. Avoid Deep Nesting in Tree Data

Keep tree data structures reasonably shallow for better performance.

9. Use table-layout: fixed for Consistent Column Widths

When precise column widths are needed:

<n-data-table
  :columns="columns"
  :data="data"
  table-layout="fixed"
/>

10. Clean Up Filters and Sorters

Provide clear actions for users to reset table state:

<n-space>
  <n-button @click="tableRef?.clearFilters()">Clear Filters</n-button>
  <n-button @click="tableRef?.clearSorter()">Clear Sorter</n-button>
</n-space>