Table
Usage
Use the rows
prop to set the data to display in the table. By default, the table will display all the fields of the rows.
Id | Name | Title | Role | |
---|---|---|---|---|
1 | Lindsay Walton | Front-end Developer | lindsay.walton@example.com | Member |
2 | Courtney Henry | Designer | courtney.henry@example.com | Admin |
3 | Tom Cook | Director of Product | tom.cook@example.com | Member |
4 | Whitney Francis | Copywriter | whitney.francis@example.com | Admin |
5 | Leonard Krasner | Senior Designer | leonard.krasner@example.com | Owner |
6 | Floyd Miles | Principal Designer | floyd.miles@example.com | Member |
<script setup lang="ts">
const people = [{
id: 1,
name: 'Lindsay Walton',
title: 'Front-end Developer',
email: 'lindsay.walton@example.com',
role: 'Member'
}, {
id: 2,
name: 'Courtney Henry',
title: 'Designer',
email: 'courtney.henry@example.com',
role: 'Admin'
}, {
id: 3,
name: 'Tom Cook',
title: 'Director of Product',
email: 'tom.cook@example.com',
role: 'Member'
}, {
id: 4,
name: 'Whitney Francis',
title: 'Copywriter',
email: 'whitney.francis@example.com',
role: 'Admin'
}, {
id: 5,
name: 'Leonard Krasner',
title: 'Senior Designer',
email: 'leonard.krasner@example.com',
role: 'Owner'
}, {
id: 6,
name: 'Floyd Miles',
title: 'Principal Designer',
email: 'floyd.miles@example.com',
role: 'Member'
}]
</script>
<template>
<UTable :rows="people" />
</template>
Columns
Use the columns
prop to configure which columns to display. It's an array of objects with the following properties:
label
- The label to display in the table header. Can be changed through thecolumn-attribute
prop.key
- The field to display from the row data.sortable
- Whether the column is sortable. Defaults tofalse
.direction
- The sort direction to use on first click. Defaults toasc
.class
- The class to apply to the column cells.rowClass
- The class to apply to the data column cells. Newsort
- Pass your ownsort
function. Defaults to a simple greater than / less than comparison.
ID | User name | Job position | ||
---|---|---|---|---|
1 | Lindsay Walton | Front-end Developer | lindsay.walton@example.com | Member |
2 | Courtney Henry | Designer | courtney.henry@example.com | Admin |
3 | Tom Cook | Director of Product | tom.cook@example.com | Member |
4 | Whitney Francis | Copywriter | whitney.francis@example.com | Admin |
5 | Leonard Krasner | Senior Designer | leonard.krasner@example.com | Owner |
6 | Floyd Miles | Principal Designer | floyd.miles@example.com | Member |
<script setup lang="ts">
const columns = [{
key: 'id',
label: 'ID'
}, {
key: 'name',
label: 'User name'
}, {
key: 'title',
label: 'Job position'
}, {
key: 'email',
label: 'Email'
}, {
key: 'role'
}]
const people = [{
id: 1,
name: 'Lindsay Walton',
title: 'Front-end Developer',
email: 'lindsay.walton@example.com',
role: 'Member'
}, {
id: 2,
name: 'Courtney Henry',
title: 'Designer',
email: 'courtney.henry@example.com',
role: 'Admin'
}, {
id: 3,
name: 'Tom Cook',
title: 'Director of Product',
email: 'tom.cook@example.com',
role: 'Member'
}, {
id: 4,
name: 'Whitney Francis',
title: 'Copywriter',
email: 'whitney.francis@example.com',
role: 'Admin'
}, {
id: 5,
name: 'Leonard Krasner',
title: 'Senior Designer',
email: 'leonard.krasner@example.com',
role: 'Owner'
}, {
id: 6,
name: 'Floyd Miles',
title: 'Principal Designer',
email: 'floyd.miles@example.com',
role: 'Member'
}]
</script>
<template>
<UTable :columns="columns" :rows="people" />
</template>
You can easily use the SelectMenu component to change the columns to display.
ID | Name | Title | Role | |
---|---|---|---|---|
1 | Lindsay Walton | Front-end Developer | lindsay.walton@example.com | Member |
2 | Courtney Henry | Designer | courtney.henry@example.com | Admin |
3 | Tom Cook | Director of Product | tom.cook@example.com | Member |
4 | Whitney Francis | Copywriter | whitney.francis@example.com | Admin |
5 | Leonard Krasner | Senior Designer | leonard.krasner@example.com | Owner |
6 | Floyd Miles | Principal Designer | floyd.miles@example.com | Member |
<script setup lang="ts">
const columns = [{
key: 'id',
label: 'ID'
}, {
key: 'name',
label: 'Name'
}, {
key: 'title',
label: 'Title'
}, {
key: 'email',
label: 'Email'
}, {
key: 'role',
label: 'Role'
}]
const selectedColumns = ref([...columns])
const people = [{
id: 1,
name: 'Lindsay Walton',
title: 'Front-end Developer',
email: 'lindsay.walton@example.com',
role: 'Member'
}, {
id: 2,
name: 'Courtney Henry',
title: 'Designer',
email: 'courtney.henry@example.com',
role: 'Admin'
}, {
id: 3,
name: 'Tom Cook',
title: 'Director of Product',
email: 'tom.cook@example.com',
role: 'Member'
}, {
id: 4,
name: 'Whitney Francis',
title: 'Copywriter',
email: 'whitney.francis@example.com',
role: 'Admin'
}, {
id: 5,
name: 'Leonard Krasner',
title: 'Senior Designer',
email: 'leonard.krasner@example.com',
role: 'Owner'
}, {
id: 6,
name: 'Floyd Miles',
title: 'Principal Designer',
email: 'floyd.miles@example.com',
role: 'Member'
}]
</script>
<template>
<div>
<div class="flex px-3 py-3.5 border-b border-gray-200 dark:border-gray-700">
<USelectMenu v-model="selectedColumns" :options="columns" multiple placeholder="Columns" />
</div>
<UTable :columns="selectedColumns" :rows="people" />
</div>
</template>
You can apply styles to tr
and td
elements by passing a class
to rows.
Also, you can apply styles to th
elements by passing a class
to columns.
# | Quantity | Name |
---|---|---|
1 | 100 | Apple |
2 | 0 | Orange |
3 | 30 | Banana |
4 | 5 | Mango |
<script setup lang="ts">
const columns = [{
key: 'id',
label: '#'
}, {
key: 'quantity',
label: 'Quantity',
class: 'italic'
}, {
key: 'name',
label: 'Name'
}]
const items = [{
id: 1,
name: 'Apple',
quantity: { value: 100, class: 'bg-green-500/50 dark:bg-green-400/50' }
}, {
id: 2,
name: 'Orange',
quantity: { value: 0 },
class: 'bg-red-500/50 dark:bg-red-400/50 animate-pulse'
}, {
id: 3,
name: 'Banana',
quantity: { value: 30, class: 'bg-green-500/50 dark:bg-green-400/50' }
}, {
id: 4,
name: 'Mango',
quantity: { value: 5, class: 'bg-green-500/50 dark:bg-green-400/50' }
}]
</script>
<template>
<UTable :rows="items" :columns="columns">
<template #quantity-data="{ row }">
{{ row.quantity.value }}
</template>
</UTable>
</template>
Sortable
You can make the columns sortable by setting the sortable
property to true
in the column configuration.
You may specify the default direction of each column through the direction
property. It can be either asc
or desc
, but it will default to asc
.
ID | Role | |||
---|---|---|---|---|
1 | Lindsay Walton | Front-end Developer | lindsay.walton@example.com | Member |
2 | Courtney Henry | Designer | courtney.henry@example.com | Admin |
3 | Tom Cook | Director of Product | tom.cook@example.com | Member |
4 | Whitney Francis | Copywriter | whitney.francis@example.com | Admin |
5 | Leonard Krasner | Senior Designer | leonard.krasner@example.com | Owner |
6 | Floyd Miles | Principal Designer | floyd.miles@example.com | Member |
<script setup lang="ts">
const columns = [{
key: 'id',
label: 'ID'
}, {
key: 'name',
label: 'Name',
sortable: true
}, {
key: 'title',
label: 'Title',
sortable: true
}, {
key: 'email',
label: 'Email',
sortable: true,
direction: 'desc' as const
}, {
key: 'role',
label: 'Role'
}]
const people = [{
id: 1,
name: 'Lindsay Walton',
title: 'Front-end Developer',
email: 'lindsay.walton@example.com',
role: 'Member'
}, {
id: 2,
name: 'Courtney Henry',
title: 'Designer',
email: 'courtney.henry@example.com',
role: 'Admin'
}, {
id: 3,
name: 'Tom Cook',
title: 'Director of Product',
email: 'tom.cook@example.com',
role: 'Member'
}, {
id: 4,
name: 'Whitney Francis',
title: 'Copywriter',
email: 'whitney.francis@example.com',
role: 'Admin'
}, {
id: 5,
name: 'Leonard Krasner',
title: 'Senior Designer',
email: 'leonard.krasner@example.com',
role: 'Owner'
}, {
id: 6,
name: 'Floyd Miles',
title: 'Principal Designer',
email: 'floyd.miles@example.com',
role: 'Member'
}]
</script>
<template>
<UTable :columns="columns" :rows="people" />
</template>
Default sorting
You can specify a default sort for the table through the sort
prop. It's an object with the following properties:
column
- The column to sort by.direction
- The sort direction. Can be eitherasc
ordesc
and defaults toasc
.
This will set the default sort and will work even if no column is set as sortable
.
<script setup lang="ts">
const sort = ref({
column: 'name',
direction: 'desc'
})
const columns = [{
label: 'Name',
key: 'name',
sortable: true
}]
const people = [{
id: 1,
name: 'Lindsay Walton',
title: 'Front-end Developer',
email: 'lindsay.walton@example.com',
role: 'Member'
}, {
id: 2,
name: 'Courtney Henry',
title: 'Designer',
email: 'courtney.henry@example.com',
role: 'Admin'
}]
</script>
<template>
<UTable :sort="sort" :columns="columns" :rows="people" />
</template>
Reactive sorting
You can use a v-model:sort
to make the sorting reactive. You may also use @update:sort
to call your own function with the sorting data.
When fetching data from an API, we can take advantage of the useFetch
or useAsyncData
composables to fetch the data based on the sorting column and direction every time the sort
reactive element changes.
When doing so, you might want to set the sort-mode
prop to manual
to disable the automatic sorting and return the rows as is.
<script setup lang="ts">
// Ensure it uses `ref` instead of `reactive`.
const sort = ref({
column: 'name',
direction: 'desc'
})
const columns = [{
label: 'Name',
key: 'name',
sortable: true
}]
const { data, pending } = await useLazyFetch(() => `/api/users?orderBy=${sort.value.column}&order=${sort.value.direction}`)
</script>
<template>
<UTable v-model:sort="sort" :loading="pending" :columns="columns" :rows="data" sort-mode="manual" />
</template>
useLazyFetch
here to make the url reactive but you can use the query
/ params
options alongside watch
.Custom sorting
Use the sort-button
prop to customize the sort button in the header. You can pass all the props of the Button component to customize it through this prop or globally through ui.table.default.sortButton
. Its icon defaults to i-heroicons-arrows-up-down-20-solid
.
ID | Role | |||
---|---|---|---|---|
1 | Lindsay Walton | Front-end Developer | lindsay.walton@example.com | Member |
2 | Courtney Henry | Designer | courtney.henry@example.com | Admin |
3 | Tom Cook | Director of Product | tom.cook@example.com | Member |
4 | Whitney Francis | Copywriter | whitney.francis@example.com | Admin |
5 | Leonard Krasner | Senior Designer | leonard.krasner@example.com | Owner |
6 | Floyd Miles | Principal Designer | floyd.miles@example.com | Member |
<template>
<UTable
sort-asc-icon="i-heroicons-arrow-up-20-solid"
sort-desc-icon="i-heroicons-arrow-down-20-solid"
:sort-button="{ icon: 'i-heroicons-sparkles-20-solid', color: 'primary', variant: 'outline', size: '2xs', square: false, ui: { rounded: 'rounded-full' } }"
class="w-full"
:columns="[{ key: 'id', label: 'ID' }, { key: 'name', label: 'Name', sortable: true }, { key: 'title', label: 'Title', sortable: true }, { key: 'email', label: 'Email', sortable: true }, { key: 'role', label: 'Role' }]"
:rows="[{ id: 1, name: 'Lindsay Walton', title: 'Front-end Developer', email: 'lindsay.walton@example.com', role: 'Member' }, { id: 2, name: 'Courtney Henry', title: 'Designer', email: 'courtney.henry@example.com', role: 'Admin' }, { id: 3, name: 'Tom Cook', title: 'Director of Product', email: 'tom.cook@example.com', role: 'Member' }, { id: 4, name: 'Whitney Francis', title: 'Copywriter', email: 'whitney.francis@example.com', role: 'Admin' }, { id: 5, name: 'Leonard Krasner', title: 'Senior Designer', email: 'leonard.krasner@example.com', role: 'Owner' }, { id: 6, name: 'Floyd Miles', title: 'Principal Designer', email: 'floyd.miles@example.com', role: 'Member' }]"
/>
</template>
Use the sort-asc-icon
prop to set a different icon or change it globally in ui.table.default.sortAscIcon
. Defaults to i-heroicons-bars-arrow-up-20-solid
.
Use the sort-desc-icon
prop to set a different icon or change it globally in ui.table.default.sortDescIcon
. Defaults to i-heroicons-bars-arrow-down-20-solid
.
Selectable
Use a v-model
to make the table selectable. The v-model
will be an array of the selected rows.
Id | Name | Title | Role | ||
---|---|---|---|---|---|
1 | Lindsay Walton | Front-end Developer | lindsay.walton@example.com | Member | |
2 | Courtney Henry | Designer | courtney.henry@example.com | Admin | |
3 | Tom Cook | Director of Product | tom.cook@example.com | Member | |
4 | Whitney Francis | Copywriter | whitney.francis@example.com | Admin | |
5 | Leonard Krasner | Senior Designer | leonard.krasner@example.com | Owner | |
6 | Floyd Miles | Principal Designer | floyd.miles@example.com | Member |
<script setup lang="ts">
const people = [{
id: 1,
name: 'Lindsay Walton',
title: 'Front-end Developer',
email: 'lindsay.walton@example.com',
role: 'Member'
}, {
id: 2,
name: 'Courtney Henry',
title: 'Designer',
email: 'courtney.henry@example.com',
role: 'Admin'
}, {
id: 3,
name: 'Tom Cook',
title: 'Director of Product',
email: 'tom.cook@example.com',
role: 'Member'
}, {
id: 4,
name: 'Whitney Francis',
title: 'Copywriter',
email: 'whitney.francis@example.com',
role: 'Admin'
}, {
id: 5,
name: 'Leonard Krasner',
title: 'Senior Designer',
email: 'leonard.krasner@example.com',
role: 'Owner'
}, {
id: 6,
name: 'Floyd Miles',
title: 'Principal Designer',
email: 'floyd.miles@example.com',
role: 'Member'
}]
const selected = ref([people[1]])
</script>
<template>
<UTable v-model="selected" :rows="people" />
</template>
by
prop to compare objects by a field instead of comparing object instances. We've replicated the behavior of Headless UI Combobox.You can also add a select
listener on your Table to make the rows clickable. The function will receive the row as the first argument.
You can use this to navigate to a page, open a modal or even to select the row manually.
Id | Name | Title | Role | ||
---|---|---|---|---|---|
1 | Lindsay Walton | Front-end Developer | lindsay.walton@example.com | Member | |
2 | Courtney Henry | Designer | courtney.henry@example.com | Admin | |
3 | Tom Cook | Director of Product | tom.cook@example.com | Member | |
4 | Whitney Francis | Copywriter | whitney.francis@example.com | Admin | |
5 | Leonard Krasner | Senior Designer | leonard.krasner@example.com | Owner |
<script setup lang="ts">
const people = [{
id: 1,
name: 'Lindsay Walton',
title: 'Front-end Developer',
email: 'lindsay.walton@example.com',
role: 'Member'
}, {
id: 2,
name: 'Courtney Henry',
title: 'Designer',
email: 'courtney.henry@example.com',
role: 'Admin'
}, {
id: 3,
name: 'Tom Cook',
title: 'Director of Product',
email: 'tom.cook@example.com',
role: 'Member'
}, {
id: 4,
name: 'Whitney Francis',
title: 'Copywriter',
email: 'whitney.francis@example.com',
role: 'Admin'
}, {
id: 5,
name: 'Leonard Krasner',
title: 'Senior Designer',
email: 'leonard.krasner@example.com',
role: 'Owner'
}]
function select (row) {
const index = selected.value.findIndex((item) => item.id === row.id)
if (index === -1) {
selected.value.push(row)
} else {
selected.value.splice(index, 1)
}
}
const selected = ref([people[1]])
</script>
<template>
<UTable v-model="selected" :rows="people" @select="select" />
</template>
Searchable
You can easily use the Input component to filter the rows.
ID | Name | Title | Role | |
---|---|---|---|---|
1 | Lindsay Walton | Front-end Developer | lindsay.walton@example.com | Member |
2 | Courtney Henry | Designer | courtney.henry@example.com | Admin |
3 | Tom Cook | Director of Product | tom.cook@example.com | Member |
4 | Whitney Francis | Copywriter | whitney.francis@example.com | Admin |
5 | Leonard Krasner | Senior Designer | leonard.krasner@example.com | Owner |
6 | Floyd Miles | Principal Designer | floyd.miles@example.com | Member |
<script setup lang="ts">
const columns = [{
key: 'id',
label: 'ID'
}, {
key: 'name',
label: 'Name'
}, {
key: 'title',
label: 'Title'
}, {
key: 'email',
label: 'Email'
}, {
key: 'role',
label: 'Role'
}]
const people = [{
id: 1,
name: 'Lindsay Walton',
title: 'Front-end Developer',
email: 'lindsay.walton@example.com',
role: 'Member'
}, {
id: 2,
name: 'Courtney Henry',
title: 'Designer',
email: 'courtney.henry@example.com',
role: 'Admin'
}, {
id: 3,
name: 'Tom Cook',
title: 'Director of Product',
email: 'tom.cook@example.com',
role: 'Member'
}, {
id: 4,
name: 'Whitney Francis',
title: 'Copywriter',
email: 'whitney.francis@example.com',
role: 'Admin'
}, {
id: 5,
name: 'Leonard Krasner',
title: 'Senior Designer',
email: 'leonard.krasner@example.com',
role: 'Owner'
}, {
id: 6,
name: 'Floyd Miles',
title: 'Principal Designer',
email: 'floyd.miles@example.com',
role: 'Member'
}]
const q = ref('')
const filteredRows = computed(() => {
if (!q.value) {
return people
}
return people.filter((person) => {
return Object.values(person).some((value) => {
return String(value).toLowerCase().includes(q.value.toLowerCase())
})
})
})
</script>
<template>
<div>
<div class="flex px-3 py-3.5 border-b border-gray-200 dark:border-gray-700">
<UInput v-model="q" placeholder="Filter people..." />
</div>
<UTable :rows="filteredRows" :columns="columns" />
</div>
</template>
Paginable
You can easily use the Pagination component to paginate the rows.
Id | Name | Title | Role | |
---|---|---|---|---|
1 | Lindsay Walton | Front-end Developer | lindsay.walton@example.com | Member |
2 | Courtney Henry | Designer | courtney.henry@example.com | Admin |
3 | Tom Cook | Director of Product | tom.cook@example.com | Member |
4 | Whitney Francis | Copywriter | whitney.francis@example.com | Admin |
5 | Leonard Krasner | Senior Designer | leonard.krasner@example.com | Owner |
<script setup lang="ts">
const people = [{
id: 1,
name: 'Lindsay Walton',
title: 'Front-end Developer',
email: 'lindsay.walton@example.com',
role: 'Member'
}, {
id: 2,
name: 'Courtney Henry',
title: 'Designer',
email: 'courtney.henry@example.com',
role: 'Admin'
}, {
id: 3,
name: 'Tom Cook',
title: 'Director of Product',
email: 'tom.cook@example.com',
role: 'Member'
}, {
id: 4,
name: 'Whitney Francis',
title: 'Copywriter',
email: 'whitney.francis@example.com',
role: 'Admin'
}, {
id: 5,
name: 'Leonard Krasner',
title: 'Senior Designer',
email: 'leonard.krasner@example.com',
role: 'Owner'
}, {
id: 6,
name: 'Floyd Miles',
title: 'Principal Designer',
email: 'floyd.miles@example.com',
role: 'Member'
}, {
id: 7,
name: 'Emily Selman',
title: 'VP, User Experience',
email: '',
role: 'Admin'
}, {
id: 8,
name: 'Kristin Watson',
title: 'VP, Human Resources',
email: '',
role: 'Member'
}, {
id: 9,
name: 'Emma Watson',
title: 'Front-end Developer',
email: '',
role: 'Member'
}, {
id: 10,
name: 'John Doe',
title: 'Designer',
email: '',
role: 'Admin'
}, {
id: 11,
name: 'Jane Doe',
title: 'Director of Product',
email: '',
role: 'Member'
}, {
id: 12,
name: 'John Smith',
title: 'Copywriter',
email: '',
role: 'Admin'
}, {
id: 13,
name: 'Jane Smith',
title: 'Senior Designer',
email: '',
role: 'Owner'
}]
const page = ref(1)
const pageCount = 5
const rows = computed(() => {
return people.slice((page.value - 1) * pageCount, (page.value) * pageCount)
})
</script>
<template>
<div>
<UTable :rows="rows" />
<div class="flex justify-end px-3 py-3.5 border-t border-gray-200 dark:border-gray-700">
<UPagination v-model="page" :page-count="pageCount" :total="people.length" />
</div>
</div>
</template>
Expandable New
You can use the expand
slot to display extra information about a row. You will have access to the row
property in the slot scope.
Expand | Id | Name | Title | Role | |
---|---|---|---|---|---|
1 | Lindsay Walton | Front-end Developer | lindsay.walton@example.com | Member | |
2 | Courtney Henry | Designer | courtney.henry@example.com | Admin | |
3 | Tom Cook | Director of Product | tom.cook@example.com | Member | |
4 | Whitney Francis | Copywriter | whitney.francis@example.com | Admin | |
5 | Leonard Krasner | Senior Designer | leonard.krasner@example.com | Owner | |
6 | Floyd Miles | Principal Designer | floyd.miles@example.com | Member |
<script setup>
const people = [{
id: 1,
name: 'Lindsay Walton',
title: 'Front-end Developer',
email: 'lindsay.walton@example.com',
role: 'Member'
}, {
id: 2,
name: 'Courtney Henry',
title: 'Designer',
email: 'courtney.henry@example.com',
role: 'Admin'
}, {
id: 3,
name: 'Tom Cook',
title: 'Director of Product',
email: 'tom.cook@example.com',
role: 'Member'
}, {
id: 4,
name: 'Whitney Francis',
title: 'Copywriter',
email: 'whitney.francis@example.com',
role: 'Admin'
}, {
id: 5,
name: 'Leonard Krasner',
title: 'Senior Designer',
email: 'leonard.krasner@example.com',
role: 'Owner'
}, {
id: 6,
name: 'Floyd Miles',
title: 'Principal Designer',
email: 'floyd.miles@example.com',
role: 'Member'
}]
</script>
<template>
<UTable :rows="people">
<template #expand="{ row }">
<div class="p-4">
<pre>{{ row }}</pre>
</div>
</template>
</UTable>
</template>
Loading
Use the loading
prop to indicate that data is currently loading with an indeterminate Progress bar.
You can use the progress
prop to customize the color
and animation
of the progress bar or change them globally in ui.table.default.progress
(you can set it to null
to hide the progress bar).
If there is no rows provided, a loading state will also be displayed. You can use the loading-state
prop to customize the icon
and label
or change them globally in ui.table.default.loadingState
(you can set it to null
to hide the loading state).
ID | Name | Title | Role | |
---|---|---|---|---|
Loading... |
<template>
<UTable
loading
:loading-state="{ icon: 'i-heroicons-arrow-path-20-solid', label: 'Loading...' }"
:progress="{ color: 'primary', animation: 'carousel' }"
class="w-full"
:columns="[{ key: 'id', label: 'ID' }, { key: 'name', label: 'Name' }, { key: 'title', label: 'Title' }, { key: 'email', label: 'Email' }, { key: 'role', label: 'Role' }]"
/>
</template>
This can be easily used with Nuxt useAsyncData
composable.
<script setup lang="ts">
const columns = [...]
const { pending, data: people } = await useLazyAsyncData('people', () => $fetch('/api/people'))
</script>
<template>
<UTable :rows="people" :columns="columns" :loading="pending" />
</template>
Empty
An empty state will be displayed when there are no results.
Use the empty-state
prop to customize the icon
and label
or change them globally in ui.table.default.emptyState
.
You can also set it to null
to hide the empty state.
ID | Name | Title | Role | |
---|---|---|---|---|
No items. |
<template>
<UTable
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'No items.' }"
class="w-full"
:columns="[{ key: 'id', label: 'ID' }, { key: 'name', label: 'Name' }, { key: 'title', label: 'Title' }, { key: 'email', label: 'Email' }, { key: 'role', label: 'Role' }]"
/>
</template>
Slots
You can use slots to customize the header and data cells of the table.
<column>-header
Use the #<column>-header
slot to customize the header cell of a column. You will have access to the column
, sort
and on-sort
properties in the slot scope.
The sort
property is an object with the following properties:
field
- The field to sort by.direction
- The direction to sort by. Can beasc
ordesc
.
The on-sort
property is a function that you can call to sort the table and accepts the column as parameter.
<column>-data
Use the #<column>-data
slot to customize the data cell of a column. You will have access to the row
, column
and getRowData
properties in the slot scope.
You can for example create an extra column for actions with a Dropdown component inside or change the color of the rows based on a selection.
Name | Title | Role | |||
---|---|---|---|---|---|
Lindsay Walton | Front-end Developer | lindsay.walton@example.com | Member | ||
Courtney Henry | Designer | courtney.henry@example.com | Admin | ||
Tom Cook | Director of Product | tom.cook@example.com | Member | ||
Whitney Francis | Copywriter | whitney.francis@example.com | Admin | ||
Leonard Krasner | Senior Designer | leonard.krasner@example.com | Owner | ||
Floyd Miles | Principal Designer | floyd.miles@example.com | Member |
<script setup lang="ts">
const columns = [{
key: 'name',
label: 'Name'
}, {
key: 'title',
label: 'Title'
}, {
key: 'email',
label: 'Email'
}, {
key: 'role',
label: 'Role'
}, {
key: 'actions'
}]
const people = [{
id: 1,
name: 'Lindsay Walton',
title: 'Front-end Developer',
email: 'lindsay.walton@example.com',
role: 'Member'
}, {
id: 2,
name: 'Courtney Henry',
title: 'Designer',
email: 'courtney.henry@example.com',
role: 'Admin'
}, {
id: 3,
name: 'Tom Cook',
title: 'Director of Product',
email: 'tom.cook@example.com',
role: 'Member'
}, {
id: 4,
name: 'Whitney Francis',
title: 'Copywriter',
email: 'whitney.francis@example.com',
role: 'Admin'
}, {
id: 5,
name: 'Leonard Krasner',
title: 'Senior Designer',
email: 'leonard.krasner@example.com',
role: 'Owner'
}, {
id: 6,
name: 'Floyd Miles',
title: 'Principal Designer',
email: 'floyd.miles@example.com',
role: 'Member'
}]
const items = (row) => [
[{
label: 'Edit',
icon: 'i-heroicons-pencil-square-20-solid',
click: () => console.log('Edit', row.id)
}, {
label: 'Duplicate',
icon: 'i-heroicons-document-duplicate-20-solid'
}], [{
label: 'Archive',
icon: 'i-heroicons-archive-box-20-solid'
}, {
label: 'Move',
icon: 'i-heroicons-arrow-right-circle-20-solid'
}], [{
label: 'Delete',
icon: 'i-heroicons-trash-20-solid'
}]
]
const selected = ref([people[1]])
</script>
<template>
<UTable v-model="selected" :rows="people" :columns="columns">
<template #name-data="{ row }">
<span :class="[selected.find(person => person.id === row.id) && 'text-primary-500 dark:text-primary-400']">{{ row.name }}</span>
</template>
<template #actions-data="{ row }">
<UDropdown :items="items(row)">
<UButton color="gray" variant="ghost" icon="i-heroicons-ellipsis-horizontal-20-solid" />
</UDropdown>
</template>
</UTable>
</template>
loading-state
Use the #loading-state
slot to customize the loading state.
Name | Title | Role | ||
---|---|---|---|---|
<script setup lang="ts">
const columns = [{
key: 'name',
label: 'Name'
}, {
key: 'title',
label: 'Title'
}, {
key: 'email',
label: 'Email'
}, {
key: 'role',
label: 'Role'
}, {
key: 'actions'
}]
const people = []
const pending = ref(true)
</script>
<template>
<UTable :rows="people" :columns="columns" :loading="pending">
<template #loading-state>
<div class="flex items-center justify-center h-32">
<i class="loader --6" />
</div>
</template>
</UTable>
</template>
<style scoped>
/* https://codepen.io/jenning/pen/YzNmzaV */
.loader {
--color: rgb(var(--color-primary-400));
--size-mid: 6vmin;
--size-dot: 1.5vmin;
--size-bar: 0.4vmin;
--size-square: 3vmin;
display: block;
position: relative;
width: 50%;
display: grid;
place-items: center;
}
.loader::before,
.loader::after {
content: '';
box-sizing: border-box;
position: absolute;
}
/**
loader --6
**/
.loader.--6::before {
width: var(--size-square);
height: var(--size-square);
background-color: var(--color);
top: calc(50% - var(--size-square));
left: calc(50% - var(--size-square));
animation: loader-6 2.4s cubic-bezier(0, 0, 0.24, 1.21) infinite;
}
@keyframes loader-6 {
0%, 100% {
transform: none;
}
25% {
transform: translateX(100%);
}
50% {
transform: translateX(100%) translateY(100%);
}
75% {
transform: translateY(100%);
}
}
</style>
empty-state
Use the #empty-state
slot to customize the empty state.
Name | Title | Role | ||
---|---|---|---|---|
No one here! |
<script setup lang="ts">
const columns = [{
key: 'name',
label: 'Name'
}, {
key: 'title',
label: 'Title'
}, {
key: 'email',
label: 'Email'
}, {
key: 'role',
label: 'Role'
}, {
key: 'actions'
}]
const people = []
</script>
<template>
<UTable :rows="people" :columns="columns">
<template #empty-state>
<div class="flex flex-col items-center justify-center py-6 gap-3">
<span class="italic text-sm">No one here!</span>
<UButton label="Add people" />
</div>
</template>
</UTable>
</template>
caption
Use the #caption
slot to customize the table's caption.
Name | Title | Role | ||
---|---|---|---|---|
Lindsay Walton | Front-end Developer | lindsay.walton@example.com | Member | |
Courtney Henry | Designer | courtney.henry@example.com | Admin | |
Tom Cook | Director of Product | tom.cook@example.com | Member | |
Whitney Francis | Copywriter | whitney.francis@example.com | Admin | |
Leonard Krasner | Senior Designer | leonard.krasner@example.com | Owner | |
Floyd Miles | Principal Designer | floyd.miles@example.com | Member |
<script setup lang="ts">
const columns = [{
key: 'name',
label: 'Name'
}, {
key: 'title',
label: 'Title'
}, {
key: 'email',
label: 'Email'
}, {
key: 'role',
label: 'Role'
}, {
key: 'actions'
}]
const people = [{
id: 1,
name: 'Lindsay Walton',
title: 'Front-end Developer',
email: 'lindsay.walton@example.com',
role: 'Member'
}, {
id: 2,
name: 'Courtney Henry',
title: 'Designer',
email: 'courtney.henry@example.com',
role: 'Admin'
}, {
id: 3,
name: 'Tom Cook',
title: 'Director of Product',
email: 'tom.cook@example.com',
role: 'Member'
}, {
id: 4,
name: 'Whitney Francis',
title: 'Copywriter',
email: 'whitney.francis@example.com',
role: 'Admin'
}, {
id: 5,
name: 'Leonard Krasner',
title: 'Senior Designer',
email: 'leonard.krasner@example.com',
role: 'Owner'
}, {
id: 6,
name: 'Floyd Miles',
title: 'Principal Designer',
email: 'floyd.miles@example.com',
role: 'Member'
}]
</script>
<template>
<UTable :rows="people" :columns="columns">
<template #caption>
<caption>Employees of ACME</caption>
</template>
</UTable>
</template>
Props
{}
{}
null
config.default.progress
null
defaultComparator
[]
config.default.loadingState
config.default.emptyState
null
"label"
"auto"
config.default.sortButton as Button
config.default.sortAscIcon
config.default.sortDescIcon
config.default.expandButton as Button
false
Config
{
wrapper: 'relative overflow-x-auto',
base: 'min-w-full table-fixed',
divide: 'divide-y divide-gray-300 dark:divide-gray-700',
thead: 'relative',
tbody: 'divide-y divide-gray-200 dark:divide-gray-800',
caption: 'sr-only',
tr: {
base: '',
selected: 'bg-gray-50 dark:bg-gray-800/50',
active: 'hover:bg-gray-50 dark:hover:bg-gray-800/50 cursor-pointer',
},
th: {
base: 'text-left rtl:text-right',
padding: 'px-4 py-3.5',
color: 'text-gray-900 dark:text-white',
font: 'font-semibold',
size: 'text-sm',
},
td: {
base: 'whitespace-nowrap',
padding: 'px-4 py-4',
color: 'text-gray-500 dark:text-gray-400',
font: '',
size: 'text-sm',
},
checkbox: {
padding: 'ps-4',
},
loadingState: {
wrapper: 'flex flex-col items-center justify-center flex-1 px-6 py-14 sm:px-14',
label: 'text-sm text-center text-gray-900 dark:text-white',
icon: 'w-6 h-6 mx-auto text-gray-400 dark:text-gray-500 mb-4 animate-spin',
},
emptyState: {
wrapper: 'flex flex-col items-center justify-center flex-1 px-6 py-14 sm:px-14',
label: 'text-sm text-center text-gray-900 dark:text-white',
icon: 'w-6 h-6 mx-auto text-gray-400 dark:text-gray-500 mb-4',
},
expand: {
icon: 'transform transition-transform duration-200',
},
progress: {
wrapper: 'absolute inset-x-0 -bottom-[0.5px] p-0',
},
default: {
sortAscIcon: 'i-heroicons-bars-arrow-up-20-solid',
sortDescIcon: 'i-heroicons-bars-arrow-down-20-solid',
sortButton: {
icon: 'i-heroicons-arrows-up-down-20-solid',
trailing: true,
square: true,
color: 'gray',
variant: 'ghost',
class: '-m-1.5',
},
expandButton: {
icon: 'i-heroicons-chevron-down',
color: 'gray',
variant: 'ghost',
size: 'xs',
class: '-my-1.5 align-middle',
},
checkbox: {
color: 'primary',
},
progress: {
color: 'primary',
animation: 'carousel',
},
loadingState: {
icon: 'i-heroicons-arrow-path-20-solid',
label: 'Loading...',
},
emptyState: {
icon: 'i-heroicons-circle-stack-20-solid',
label: 'No items.',
},
},
}
Example
Here is an example of a Table component with all its features implemented.
Todos
Actions | ||||
---|---|---|---|---|
1 | delectus aut autem | In Progress | ||
2 | quis ut nam facilis et officia qui | In Progress | ||
3 | fugiat veniam minus | In Progress | ||
4 | et porro tempora | Completed | ||
5 | laboriosam mollitia et enim quasi adipisci quia provident illum | In Progress | ||
6 | qui ullam ratione quibusdam voluptatem quia omnis | In Progress | ||
7 | illo expedita consequatur quia in | In Progress | ||
8 | quo adipisci enim quam ut ab | Completed | ||
9 | molestiae perspiciatis ipsa | In Progress | ||
10 | illo est ratione doloremque quia maiores aut | Completed |