| name | n-data-table |
| description | A powerful data table component for displaying structured tabular data with features like sorting, filtering, pagination, selection, tree data, virtual scrolling, and more. |
| author | jiaiyan |
| version | 1.0.0 |
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 |
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 |
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 },
]
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>