Vuetify 3 – Data Display
v-data-table
The Vuetify 3 data table API has changed significantly from Vuetify 2.
Static data
<template>
<v-data-table
:headers="headers"
:items="users"
:search="search"
:items-per-page="10"
item-value="id"
>
<template #top>
<v-text-field v-model="search" label="Search" clearable />
</template>
<template #item.status="{ item }">
<v-chip :color="statusColor(item.status)">{{ item.status }}</v-chip>
</template>
<template #item.actions="{ item }">
<v-btn icon="mdi-pencil" size="small" @click="edit(item)" />
<v-btn icon="mdi-delete" size="small" color="error" @click="remove(item)" />
</template>
</v-data-table>
</template>
<script>
export default {
data() {
return {
search: '',
headers: [
{ title: 'Name', key: 'name', sortable: true },
{ title: 'Email', key: 'email' },
{ title: 'Status', key: 'status' },
{ title: '', key: 'actions', sortable: false, width: 120 },
],
users: [
{ id: 1, name: 'Ada', email: 'ada@kyd.au', status: 'active' },
{ id: 2, name: 'Grace', email: 'grace@kyd.au', status: 'inactive' },
],
}
},
methods: {
statusColor(s) { return s === 'active' ? 'success' : 'grey' },
edit(item) { /* ... */ },
remove(item) { /* ... */ },
},
}
</script>
Important Vuetify 3 changes
headersusestitleandkey(wastextandvaluein Vuetify 2)- Slot names are
item.<key>(wasitem.<value>in Vuetify 2) item-valuedefaults toidif omitted; set explicitly when your PK is named differently- Pass plain arrays — no
<v-data-table-server>needed unless you do server-side pagination
Server-side pagination → v-data-table-server
<v-data-table-server
v-model:items-per-page="itemsPerPage"
:headers="headers"
:items="rows"
:items-length="total"
:loading="loading"
@update:options="onOptions"
/>
methods: {
async onOptions({ page, itemsPerPage, sortBy }) {
this.loading = true
try {
const { rows, total } = await api.list({
page, rowsPerPage: itemsPerPage,
sortBy: sortBy[0]?.key,
descending: sortBy[0]?.order === 'desc',
})
this.rows = rows
this.total = total
} finally {
this.loading = false
}
},
}
v-list
<v-list density="compact" nav>
<v-list-subheader>Filters</v-list-subheader>
<v-list-item
v-for="filter in filters"
:key="filter.id"
:title="filter.name"
:subtitle="filter.count + ' items'"
:prepend-icon="filter.icon"
@click="apply(filter)"
/>
</v-list>
Selection model:
<v-list v-model:selected="selected" select-strategy="single">
<v-list-item v-for="i in items" :key="i.id" :value="i.id" :title="i.name" />
</v-list>
v-card
<v-card variant="elevated">
<v-card-title>User</v-card-title>
<v-card-subtitle>{{ user.email }}</v-card-subtitle>
<v-card-text>
<p>Member since {{ user.joined }}</p>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn variant="text" @click="cancel">Cancel</v-btn>
<v-btn color="primary" @click="save">Save</v-btn>
</v-card-actions>
</v-card>
variant options: elevated (default), flat, tonal, outlined, plain.
v-chip
<v-chip color="primary" size="small">tag</v-chip>
<v-chip closable @click:close="remove">tag</v-chip>
v-pagination
<v-pagination v-model="page" :length="totalPages" :total-visible="7" />
Pair with manual pagination of a list (v-data-table-server has its own
built-in pagination, don't double up).