--- title: Data Table description: Powerful table and datagrids built using TanStack Table. primitive: https://tanstack.com/table/v8/docs/guide/introduction --- ## Introduction Every data table or datagrid I've created has been unique. They all behave differently, have specific sorting and filtering requirements, and work with different data sources. It doesn't make sense to combine all of these variations into a single component. If we do that, we'll lose the flexibility that [headless UI](https://tanstack.com/table/latest/docs/introduction#what-is-headless-ui) provides. So instead of a data-table component, I thought it would be more helpful to provide a guide on how to build your own. We'll start with the basic `` component and build a complex data table from scratch. **Tip:** If you find yourself using the same table in multiple places in your app, you can always extract it into a reusable component. ## Table of Contents This guide will show you how to use [TanStack Table](https://tanstack.com/table/v8) and the `
` component to build your own custom data table. We'll cover the following topics: - [Basic Table](#basic-table) - [Row Actions](#row-actions) - [Pagination](#pagination) - [Sorting](#sorting) - [Filtering](#filtering) - [Visibility](#visibility) - [Row Selection](#row-selection) - [Reusable Components](#reusable-components) ## Installation 1. Add the `
` component to your project: ```bash npx shadcn-vue@latest add table ``` 2. Add `tanstack/vue-table` dependency: ```bash npm install @tanstack/vue-table ``` ## Examples ### Column Pinning ### Reactive Table A reactive table was added in `v8.20.0` of the TanStack Table. You can see the [docs](https://tanstack.com/table/latest/docs/framework/vue/guide/table-state#using-reactive-data) for more information. We added an example where we are randomizing `status` column. One main point is that you need to mutate **full** data, as it is a `shallowRef` object. > __*⚠️ `shallowRef` is used under the hood for performance reasons, meaning that the data is not deeply reactive, only the `.value` is. To update the data you have to mutate the data directly.*__ Relative PR: [Tanstack/table #5687](https://github.com/TanStack/table/pull/5687#issuecomment-2281067245) If you want to mutate `props.data`, you should use [`defineModel`](https://vuejs.org/api/sfc-script-setup.html#definemodel). There is no difference between using `ref` or `shallowRef` for your data object; it will be automatically mutated by the TanStack Table to `shallowRef`. ## Prerequisites We are going to build a table to show recent payments. Here's what our data looks like: ```ts:line-numbers interface Payment { id: string amount: number status: 'pending' | 'processing' | 'success' | 'failed' email: string } export const payments: Payment[] = [ { id: '728ed52f', amount: 100, status: 'pending', email: 'm@example.com', }, { id: '489e1d42', amount: 125, status: 'processing', email: 'example@gmail.com', }, // ... ] ``` ## Project Structure Start by creating the following file structure: ```ansi components └── payments ├── columns.ts ├── data-table.vue ├── data-table-dropdown.vue └── app.vue ``` I'm using a Nuxt example here but this works for any other Vue framework. - `columns.ts` It will contain our column definitions. - `data-table.vue` It will contain our `` component. - `data-table-dropdown.vue` It will contain our `` component. - `app.vue` This is where we'll fetch data and render our table. ## Basic Table Let's start by building a basic table. ### Column Definitions First, we'll define our columns in the `columns.ts` file. ```ts:line-numbers import { h } from 'vue' export const columns: ColumnDef[] = [ { accessorKey: 'amount', header: () => h('div', { class: 'text-right' }, 'Amount'), cell: ({ row }) => { const amount = Number.parseFloat(row.getValue('amount')) const formatted = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', }).format(amount) return h('div', { class: 'text-right font-medium' }, formatted) }, } ] ``` **Note:** Columns are where you define the core of what your table will look like. They define the data that will be displayed, how it will be formatted, sorted and filtered. ### `` component Next, we'll create a `` component to render our table. ```vue ``` **Tip**: If you find yourself using `` in multiple places, this is the component you could make reusable by extracting it to `components/ui/data-table.vue`. `` ### Render the table Finally, we'll render our table in our index component. ```vue ``` ## Cell Formatting Let's format the amount cell to display the dollar amount. We'll also align the cell to the right. ### Update columns definition Update the `header` and `cell` definitions for amount as follows: ```ts import { h } from 'vue' export const columns: ColumnDef[] = [ { accessorKey: 'amount', header: () => h('div', { class: 'text-right' }, 'Amount'), cell: ({ row }) => { const amount = Number.parseFloat(row.getValue('amount')) const formatted = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', }).format(amount) return h('div', { class: 'text-right font-medium' }, formatted) }, } ] ``` You can use the same approach to format other cells and headers. ## Row Actions Let's add row actions to our table. We'll use a `` component for this. ### Add the following into your `DataTableDropDown.vue` component ```vue ``` ### Update columns definition Update our columns definition to add a new `actions` column. The `actions` cell returns a `` component. ```ts import DropdownAction from '@/components/DataTableDropDown.vue' import { ColumnDef } from '@tanstack/vue-table' export const columns: ColumnDef[] = [ // ... { id: 'actions', enableHiding: false, cell: ({ row }) => { const payment = row.original return h('div', { class: 'relative' }, h(DropdownAction, { payment, })) }, }, ] ``` You can access the row data using `row.original` in the `cell` function. Use this to handle actions for your row eg. use the `id` to make a DELETE call to your API. ## Pagination Next, we'll add pagination to our table. ### Update `` ```ts:line-numbers {4,12} import { FlexRender, getCoreRowModel, getPaginationRowModel, useVueTable, } from "@tanstack/vue-table" const table = useVueTable({ get data() { return props.data }, get columns() { return props.columns }, getCoreRowModel: getCoreRowModel(), getPaginationRowModel: getPaginationRowModel(), }) ``` This will automatically paginate your rows into pages of 10. See the [pagination docs](https://tanstack.com/table/v8/docs/api/features/pagination) for more information on customizing page size and implementing manual pagination. ### Add pagination controls We can add pagination controls to our table using the `