Skip to main content

Data Table

The VDataTable component is used for displaying tabular data. It includes features such as sorting, searching, pagination, row selection, expandable rows, and grouping.

Usage

<template>
  <v-data-table
    :headers="headers"
    :items="desserts"
  />
</template>

<script setup>
const headers = [
  { title: 'Dessert', key: 'name' },
  { title: 'Calories', key: 'calories' },
  { title: 'Fat (g)', key: 'fat' },
]

const desserts = [
  { name: 'Frozen Yogurt', calories: 159, fat: 6.0 },
  { name: 'Ice cream sandwich', calories: 237, fat: 9.0 },
  { name: 'Eclair', calories: 262, fat: 16.0 },
]
</script>

Props

headers
DataTableHeader[]
Array of header definitions. Each header can have: title, key, value, sortable, align, width, minWidth, maxWidth, fixed, sort, filter
items
any[]
Array of data items to display in the table
itemValue
string | function
default:"id"
Property or function to use as the unique identifier for each item
Text to filter the items
sortBy
SortItem[]
Array of sort configurations. Each item has key and order (asc or desc)
multiSort
boolean
default:"false"
Allows sorting by multiple columns
mustSort
boolean
default:"false"
Requires at least one sort to be active
page
number
default:"1"
Current page number
itemsPerPage
number
default:"10"
Number of items to display per page. Use -1 for all items
showSelect
boolean
default:"false"
Shows checkboxes for row selection
selectStrategy
string
default:"page"
Selection strategy. Options: single, page, all
modelValue
any[]
Array of selected items (v-model)
showExpand
boolean
default:"false"
Shows expand icon for expandable rows
expanded
any[]
Array of expanded items
groupBy
GroupByItem[]
Array of grouping configurations. Each item has key and order
loading
boolean | string
default:"false"
Shows a loading indicator. Can be a boolean or loading text
loadingText
string
default:"$vuetify.dataIterator.loadingText"
Text to display when loading
noDataText
string
default:"$vuetify.noDataText"
Text to display when there are no items
hideNoData
boolean
default:"false"
Hides the no data message
hideDefaultHeader
boolean
default:"false"
Hides the default header
Hides the default pagination footer
hideDefaultBody
boolean
default:"false"
Hides the default table body
fixedHeader
boolean
default:"false"
Makes the header fixed while scrolling
Makes the footer fixed
height
string | number
Sets the height of the table (enables scrolling)
hover
boolean
default:"false"
Adds hover effect on table rows
density
string
default:"default"
Adjusts row height. Options: default, comfortable, compact

Events

update:modelValue
(items: any[]) => void
Emitted when selected items change
update:page
(page: number) => void
Emitted when page changes
update:itemsPerPage
(count: number) => void
Emitted when items per page changes
update:sortBy
(sortBy: any[]) => void
Emitted when sort configuration changes
update:groupBy
(groupBy: any[]) => void
Emitted when grouping changes
update:expanded
(expanded: any[]) => void
Emitted when expanded items change

Slots

Slot for content above the table
item.{key}
Slot for customizing individual column cells. Replace {key} with the column key
header.{key}
Slot for customizing individual header cells
expanded-row
Slot for content shown when a row is expanded
no-data
Slot for custom no data message
loading
Slot for custom loading indicator
bottom
Slot for content below the table (replaces default footer)

Examples

Basic Table

<template>
  <v-data-table
    :headers="headers"
    :items="users"
    :items-per-page="5"
  />
</template>

<script setup>
const headers = [
  { title: 'Name', key: 'name', align: 'start' },
  { title: 'Email', key: 'email' },
  { title: 'Role', key: 'role' },
  { title: 'Status', key: 'status' },
]

const users = [
  { id: 1, name: 'John Doe', email: '[email protected]', role: 'Admin', status: 'Active' },
  { id: 2, name: 'Jane Smith', email: '[email protected]', role: 'User', status: 'Active' },
  { id: 3, name: 'Bob Johnson', email: '[email protected]', role: 'User', status: 'Inactive' },
]
</script>

Sortable Table

<template>
  <v-data-table
    :headers="headers"
    :items="products"
    :sort-by="[{ key: 'price', order: 'desc' }]"
    multi-sort
  />
</template>

<script setup>
const headers = [
  { title: 'Product', key: 'name', sortable: true },
  { title: 'Price', key: 'price', sortable: true },
  { title: 'Stock', key: 'stock', sortable: true },
  { title: 'Category', key: 'category', sortable: false },
]

const products = [
  { name: 'Laptop', price: 999, stock: 15, category: 'Electronics' },
  { name: 'Mouse', price: 25, stock: 100, category: 'Accessories' },
  { name: 'Keyboard', price: 75, stock: 50, category: 'Accessories' },
]
</script>

Selectable Rows

<template>
  <div>
    <v-data-table
      v-model="selected"
      :headers="headers"
      :items="items"
      show-select
      item-value="id"
    />
    <div class="mt-4">
      Selected: {{ selected.length }} items
    </div>
  </div>
</template>

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

const selected = ref([])

const headers = [
  { title: 'Name', key: 'name' },
  { title: 'Email', key: 'email' },
]

const items = [
  { id: 1, name: 'Alice', email: '[email protected]' },
  { id: 2, name: 'Bob', email: '[email protected]' },
  { id: 3, name: 'Charlie', email: '[email protected]' },
]
</script>

Custom Cell Slots

<template>
  <v-data-table
    :headers="headers"
    :items="users"
  >
    <template v-slot:item.status="{ item }">
      <v-chip
        :color="item.status === 'Active' ? 'success' : 'error'"
        size="small"
      >
        {{ item.status }}
      </v-chip>
    </template>
    
    <template v-slot:item.actions="{ item }">
      <v-btn
        icon="mdi-pencil"
        size="small"
        variant="text"
        @click="editItem(item)"
      />
      <v-btn
        icon="mdi-delete"
        size="small"
        variant="text"
        @click="deleteItem(item)"
      />
    </template>
  </v-data-table>
</template>

<script setup>
const headers = [
  { title: 'Name', key: 'name' },
  { title: 'Status', key: 'status' },
  { title: 'Actions', key: 'actions', sortable: false },
]

const users = [
  { id: 1, name: 'John Doe', status: 'Active' },
  { id: 2, name: 'Jane Smith', status: 'Inactive' },
]

function editItem(item) {
  console.log('Edit', item)
}

function deleteItem(item) {
  console.log('Delete', item)
}
</script>

Expandable Rows

<template>
  <v-data-table
    :headers="headers"
    :items="orders"
    show-expand
  >
    <template v-slot:expanded-row="{ item }">
      <tr>
        <td :colspan="headers.length + 1">
          <div class="pa-4">
            <strong>Order Details:</strong>
            <p>Customer: {{ item.customer }}</p>
            <p>Address: {{ item.address }}</p>
            <p>Notes: {{ item.notes }}</p>
          </div>
        </td>
      </tr>
    </template>
  </v-data-table>
</template>

<script setup>
const headers = [
  { title: 'Order ID', key: 'id' },
  { title: 'Date', key: 'date' },
  { title: 'Total', key: 'total' },
]

const orders = [
  { 
    id: '1001', 
    date: '2024-01-15', 
    total: '$150.00',
    customer: 'John Doe',
    address: '123 Main St',
    notes: 'Deliver after 5 PM'
  },
  { 
    id: '1002', 
    date: '2024-01-16', 
    total: '$89.99',
    customer: 'Jane Smith',
    address: '456 Oak Ave',
    notes: 'Ring doorbell'
  },
]
</script>

Grouped Table

<template>
  <v-data-table
    :headers="headers"
    :items="employees"
    :group-by="[{ key: 'department', order: 'asc' }]"
  />
</template>

<script setup>
const headers = [
  { title: 'Name', key: 'name' },
  { title: 'Position', key: 'position' },
  { title: 'Department', key: 'department' },
]

const employees = [
  { name: 'Alice Johnson', position: 'Developer', department: 'Engineering' },
  { name: 'Bob Smith', position: 'Designer', department: 'Design' },
  { name: 'Charlie Brown', position: 'Developer', department: 'Engineering' },
  { name: 'Diana Prince', position: 'Manager', department: 'Design' },
]
</script>

Build docs developers (and LLMs) love