Data

Table

Display data in a 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.

IdNameTitleEmailRole
1Lindsay WaltonFront-end Developerlindsay.walton@example.comMember
2Courtney HenryDesignercourtney.henry@example.comAdmin
3Tom CookDirector of Producttom.cook@example.comMember
4Whitney FrancisCopywriterwhitney.francis@example.comAdmin
5Leonard KrasnerSenior Designerleonard.krasner@example.comOwner
6Floyd MilesPrincipal Designerfloyd.miles@example.comMember
<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>

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 the column-attribute prop.
  • key - The field to display from the row data.
  • sortable - Whether the column is sortable. Defaults to false.
  • direction - The sort direction to use on first click. Defaults to asc.
  • class - The class to apply to the column cells.
IDUser nameJob positionEmail
1Lindsay WaltonFront-end Developerlindsay.walton@example.comMember
2Courtney HenryDesignercourtney.henry@example.comAdmin
3Tom CookDirector of Producttom.cook@example.comMember
4Whitney FrancisCopywriterwhitney.francis@example.comAdmin
5Leonard KrasnerSenior Designerleonard.krasner@example.comOwner
6Floyd MilesPrincipal Designerfloyd.miles@example.comMember
<script setup>
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.

IDNameTitleEmailRole
1Lindsay WaltonFront-end Developerlindsay.walton@example.comMember
2Courtney HenryDesignercourtney.henry@example.comAdmin
3Tom CookDirector of Producttom.cook@example.comMember
4Whitney FrancisCopywriterwhitney.francis@example.comAdmin
5Leonard KrasnerSenior Designerleonard.krasner@example.comOwner
6Floyd MilesPrincipal Designerfloyd.miles@example.comMember
<script setup>
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>

Sortable

You can make the columns sortable by setting the sortable property to true in the column configuration.

IDRole
4Whitney FrancisCopywriterwhitney.francis@example.comAdmin
2Courtney HenryDesignercourtney.henry@example.comAdmin
3Tom CookDirector of Producttom.cook@example.comMember
1Lindsay WaltonFront-end Developerlindsay.walton@example.comMember
6Floyd MilesPrincipal Designerfloyd.miles@example.comMember
5Leonard KrasnerSenior Designerleonard.krasner@example.comOwner
<script setup>
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'
}, {
  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" :sort="{ column: 'title' }" />
</template>

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.

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 either asc or desc and defaults to asc.
This will set the default sort and will work even if no column is set as sortable.

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.

IDRole
1Lindsay WaltonFront-end Developerlindsay.walton@example.comMember
2Courtney HenryDesignercourtney.henry@example.comAdmin
3Tom CookDirector of Producttom.cook@example.comMember
4Whitney FrancisCopywriterwhitney.francis@example.comAdmin
5Leonard KrasnerSenior Designerleonard.krasner@example.comOwner
6Floyd MilesPrincipal Designerfloyd.miles@example.comMember
<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' }]"
/>

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.

You can also customize the entire header cell, read more in the Slots section.

Reactive sorting

Sometimes you will want to fetch new data depending on the sorted column and direction. You can use the v-model:sort to automatically update the ref reactive element every time the sorting changes on the Table. You may also use @update:sort to call your own function with the sorting data.

For example, we can take advantage of useLazyRefresh computed URL to automatically fetch the data depending on the sorting column and direction every time the sort reactive element changes.

<script setup>
// Ensure it uses `ref` instead of `reactive`.
const sort = ref({
  column: 'name',
  direction: 'desc'
})

const columns = [...]

const { data, pending } = useLazyFetch(() => {
  return `/api/users?orderBy=${sort.value.column}&order=${sort.value.direction}`
})
</script>

<template>
  <UTable v-model:sort="sort" :loading="pending" :columns="columns" :rows="data" />
</template>

The initial value of sort will be respected as the initial sort column and direction, as well as each column default sorting direction.

IDEmail
4Whitney FrancisCopywriterwhitney.francis@example.comAdmin
3Tom CookDirector of Producttom.cook@example.comMember
1Lindsay WaltonFront-end Developerlindsay.walton@example.comMember
5Leonard KrasnerSenior Designerleonard.krasner@example.comOwner
6Floyd MilesPrincipal Designerfloyd.miles@example.comMember
2Courtney HenryDesignercourtney.henry@example.comAdmin
<script setup>
const sort = ref({
  column: 'name',
  direction: 'desc'
})

const columns = [{
  key: 'id',
  label: 'ID'
}, {
  key: 'name',
  label: 'Name',
  sortable: true
}, {
  key: 'title',
  label: 'Title',
  sortable: true
}, {
  key: 'email',
  label: 'Email'
}, {
  key: 'role',
  label: 'Role',
  sortable: true,
  direction: 'desc'
}]

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 v-model:sort="sort" :columns="columns" :rows="people" />
</template>

Selectable

Use a v-model to make the table selectable. The v-model will be an array of the selected rows.

IdNameTitleEmailRole
1Lindsay WaltonFront-end Developerlindsay.walton@example.comMember
2Courtney HenryDesignercourtney.henry@example.comAdmin
3Tom CookDirector of Producttom.cook@example.comMember
4Whitney FrancisCopywriterwhitney.francis@example.comAdmin
5Leonard KrasnerSenior Designerleonard.krasner@example.comOwner
6Floyd MilesPrincipal Designerfloyd.miles@example.comMember
<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'
}]

const selected = ref([people[1]])
</script>

<template>
  <UTable v-model="selected" :rows="people" />
</template>
You can use the 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.

IdNameTitleEmailRole
1Lindsay WaltonFront-end Developerlindsay.walton@example.comMember
2Courtney HenryDesignercourtney.henry@example.comAdmin
3Tom CookDirector of Producttom.cook@example.comMember
4Whitney FrancisCopywriterwhitney.francis@example.comAdmin
5Leonard KrasnerSenior Designerleonard.krasner@example.comOwner
<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'
}]

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.

IDNameTitleEmailRole
1Lindsay WaltonFront-end Developerlindsay.walton@example.comMember
2Courtney HenryDesignercourtney.henry@example.comAdmin
3Tom CookDirector of Producttom.cook@example.comMember
4Whitney FrancisCopywriterwhitney.francis@example.comAdmin
5Leonard KrasnerSenior Designerleonard.krasner@example.comOwner
6Floyd MilesPrincipal Designerfloyd.miles@example.comMember
<script setup>
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.

IdNameTitleEmailRole
1Lindsay WaltonFront-end Developerlindsay.walton@example.comMember
2Courtney HenryDesignercourtney.henry@example.comAdmin
3Tom CookDirector of Producttom.cook@example.comMember
4Whitney FrancisCopywriterwhitney.francis@example.comAdmin
5Leonard KrasnerSenior Designerleonard.krasner@example.comOwner
<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'
}, {
  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>

Loading

Use the loading prop to display a loading state.

Use the loading-state prop to customize the icon and label or change them globally in ui.table.default.loadingState.

You can also set it to null to hide the loading state.

IDNameTitleEmailRole

Loading...

<UTable
  loading
  :loading-state="{ icon: 'i-heroicons-arrow-path-20-solid', label: 'Loading...' }"
  class="w-full"
  :columns="[{ key: 'id', label: 'ID' }, { key: 'name', label: 'Name' }, { key: 'title', label: 'Title' }, { key: 'email', label: 'Email' }, { key: 'role', label: 'Role' }]"
/>

This can be easily used with Nuxt useAsyncData composable.

<script setup>
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.

IDNameTitleEmailRole

No items.

<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' }]"
/>

Styling

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.

#QuantityName
1100Apple
20Orange
330Banana
45Mango
<script setup>
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>

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 be asc or desc.

The on-sort property is a function that you can call to sort the table and accepts the column as parameter.

Even though you can customize the sort button as mentioned in the Sortable section, you can use this slot to completely override its behavior, with a custom dropdown for example.

<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.

NameTitleEmailRole
Lindsay WaltonFront-end Developerlindsay.walton@example.comMember
Courtney HenryDesignercourtney.henry@example.comAdmin
Tom CookDirector of Producttom.cook@example.comMember
Whitney FrancisCopywriterwhitney.francis@example.comAdmin
Leonard KrasnerSenior Designerleonard.krasner@example.comOwner
Floyd MilesPrincipal Designerfloyd.miles@example.comMember
<script setup>
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.

NameTitleEmailRole
<script setup>
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.

NameTitleEmailRole
No one here!
<script setup>
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>

Props

sort
{ column: string; direction: "asc" | "desc"; }
{}
ui
{}
{}
modelValue
unknown[]
null
rows
{ [key: string]: any; click?: Function; }[]
[]
loadingState
{ icon: string; label: string; }
config.default.loadingState
emptyState
{ icon: string; label: string; }
config.default.emptyState
by
string | Function
defaultComparator
columns
{ [key: string]: any; key: string; sortable?: boolean; direction?: "asc" | "desc"; class?: string; }[]
null
columnAttribute
string
"label"
sortButton
Button
config.default.sortButton as Button
sortAscIcon
string
config.default.sortAscIcon
sortDescIcon
string
config.default.sortDescIcon
loading
boolean
false

Config

{
  "wrapper": "relative overflow-x-auto",
  "base": "min-w-full table-fixed",
  "divide": "divide-y divide-gray-300 dark:divide-gray-700",
  "thead": "",
  "tbody": "divide-y divide-gray-200 dark:divide-gray-800",
  "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-3 py-3.5",
    "color": "text-gray-900 dark:text-white",
    "font": "font-semibold",
    "size": "text-sm"
  },
  "td": {
    "base": "whitespace-nowrap",
    "padding": "px-3 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"
  },
  "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"
    },
    "loadingState": {
      "icon": "i-heroicons-arrow-path-20-solid",
      "label": "Loading..."
    },
    "emptyState": {
      "icon": "i-heroicons-circle-stack-20-solid",
      "label": "No items."
    }
  }
}