---
description: 'Display data in a table.'
links:
  - label: GitHub
    icon: i-simple-icons-github
    to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/data/Table.vue
---

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

::component-example{class="grid"}
---
padding: false
overflowClass: 'overflow-x-auto'
---

#default
:table-example-basic{class="flex-1"}

#code
```vue
<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.

::component-example{class="grid"}
---
padding: false
overflowClass: 'overflow-x-auto'
---

#default
:table-example-columns{class="flex-1"}

#code
```vue
<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 = [...]
</script>

<template>
  <UTable :columns="columns" :rows="people" />
</template>
```
::

You can easily use the [SelectMenu](/forms/select-menu) component to change the columns to display.

::component-example{class="grid"}
---
padding: false
overflowClass: 'overflow-x-auto'
---

#default
:table-example-columns-selectable{class="flex-1"}

#code
```vue
<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 = [...]
</script>

<template>
  <div>
    <USelectMenu v-model="selectedColumns" :options="columns" multiple placeholder="Columns" />

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

::component-example{class="grid"}
---
padding: false
overflowClass: 'overflow-x-auto'
---

#default
:table-example-columns-sortable{class="flex-1"}

#code
```vue
<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 = [...]
</script>

<template>
  <UTable :columns="columns" :rows="people" :sort="{ column: 'title' }" />
</template>
```
::

You can specify the default direction of each column through the `direction` property. It can be either `asc` or `desc` and defaults 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`.

::callout{icon="i-heroicons-light-bulb"}
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](/elements/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`.

::component-card{class="grid"}
---
padding: false
overflowClass: 'overflow-x-auto'
baseProps:
  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'
props:
  sortAscIcon: 'i-heroicons-arrow-up-20-solid'
  sortDescIcon: 'i-heroicons-arrow-down-20-solid'
  sortButton:
    icon: 'i-heroicons-sparkles-20-solid'
    color: 'primary'
    variant: 'outline'
    size: '2xs'
    square: false
    ui:
      rounded: 'rounded-full'
excludedProps:
  - sortButton
  - sortAscIcon
  - sortDescIcon
---
::

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

::callout{icon="i-heroicons-light-bulb"}
You can also customize the entire header cell, read more in the [Slots](#slots) section.
::

### Selectable

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

::component-example{class="grid"}
---
padding: false
overflowClass: 'overflow-x-auto'
---

#default
:table-example-selectable{class="flex-1"}

#code
```vue
<script setup>
const people = [...]

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

<template>
  <UTable v-model="selected" :rows="people" />
</template>
```
::

::callout{icon="i-heroicons-light-bulb"}
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](https://headlessui.com/vue/combobox#binding-objects-as-values).
::

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.

::component-example{class="grid"}
---
padding: false
overflowClass: 'overflow-x-auto'
---

#default
:table-example-clickable{class="flex-1"}

#code
```vue
<script setup>
const people = [...]

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](/forms/input) component to filter the rows.

::component-example{class="grid"}
---
padding: false
overflowClass: 'overflow-x-auto'
---

#default
:table-example-searchable{class="flex-1"}

#code
```vue
<script setup>
const people = [...]

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>
    <UInput v-model="q" placeholder="Filter people..." />

    <UTable :rows="filteredRows" />
  </div>
</template>
```
::

### Paginable

You can easily use the [Pagination](/navigation/pagination) component to paginate the rows.

::component-example
---
padding: false
---

#default
:table-example-paginable{class="w-full"}

#code
```vue
<script setup>
const people = [...]

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" />

    <UPagination v-model="page" :page-count="pageCount" :total="people.length" />
  </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.

::component-card
---
padding: false
overflowClass: 'overflow-x-auto'
baseProps:
  class: 'w-full'
  columns:
    - key: 'id'
      label: 'ID'
    - key: 'name'
      label: 'Name'
    - key: 'title'
      label: 'Title'
    - key: 'email'
      label: 'Email'
    - key: 'role'
      label: 'Role'
props:
  loading: true
  loadingState:
    icon: 'i-heroicons-arrow-path-20-solid'
    label: "Loading..."
excludedProps:
  - loadingState
---
::

This can be easily used with Nuxt `useAsyncData` composable.

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

::component-card
---
padding: false
overflowClass: 'overflow-x-auto'
baseProps:
  class: 'w-full'
  columns:
    - key: 'id'
      label: 'ID'
    - key: 'name'
      label: 'Name'
    - key: 'title'
      label: 'Title'
    - key: 'email'
      label: 'Email'
    - key: 'role'
      label: 'Role'
props:
  emptyState:
    icon: 'i-heroicons-circle-stack-20-solid'
    label: "No items."
excludedProps:
  - emptyState
---
::

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

::callout{icon="i-heroicons-light-bulb"}
Even though you can customize the sort button as mentioned in the [Sortable](#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](/elements/dropdown) component inside or change the color of the rows based on a selection.

::component-example{class="grid"}
---
padding: false
overflowClass: 'overflow-x-auto'
---

#default
:table-example-slots{class="flex-1"}

#code
```vue
<script setup>
const columns = [..., {
  key: 'actions'
}]

const people = [...]

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.

::component-example{class="grid"}
---
padding: false
overflowClass: 'overflow-x-auto'
---

#default
:table-example-loading-slot{class="flex-1"}

#code
```vue
<script setup>
const columns = [...]

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 */
</style>
```
::

### `empty-state`

Use the `#empty-state` slot to customize the empty state.

::component-example{class="grid"}
---
padding: false
overflowClass: 'overflow-x-auto'
---

#default
:table-example-empty-slot{class="flex-1"}

#code
```vue
<script setup>
const columns = [...]
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

:component-props

## Preset

:component-preset
