Table

Ein responsives Tabellenelement, um Daten in Zeilen und Spalten anzuzeigen.

Bearbeiten

Die Table-Komponente baut auf der TanStack-Tabelle v8 auf und wird von der useVueTable composable unterstützt, um eine flexible und vollständig typsichere API bereitzustellen.

Es rendert Ihre Daten als Zeilen und Spalten und unterstützt Sortierung, Filterung, Paginierung, Zeilenauswahl, Erweiterung, Gruppierung, Pinning und Virtualisierung, sodass Sie alles von einer einfachen Datentabelle bis zu einem voll ausgestatteten Datenraster erstellen können.

#DateStatusAmount
#4600Mar 11, 15:30paidjames.anderson@example.com€594.00
#4599Mar 11, 10:10failedmia.white@example.com€276.00
#4598Mar 11, 08:50refundedwilliam.brown@example.com€315.00
#4597Mar 10, 19:45paidemma.davis@example.com€529.00
#4596Mar 10, 15:55paidethan.harris@example.com€639.00
#4595Mar 10, 13:40refundedava.thomas@example.com€428.00
#4594Mar 10, 09:15paidmichael.wilson@example.com€683.00
#4593Mar 9, 20:25failedolivia.taylor@example.com€947.00
#4592Mar 9, 18:45paidbenjamin.jackson@example.com€851.00
#4591Mar 9, 16:05paidsophia.miller@example.com€762.00
#4590Mar 9, 14:20paidnoah.clark@example.com€573.00
#4589Mar 9, 11:35failedisabella.lee@example.com€389.00
#4588Mar 8, 22:50refundedliam.walker@example.com€701.00
#4587Mar 8, 20:15paidcharlotte.hall@example.com€856.00
#4586Mar 8, 17:40paidmason.young@example.com€492.00
#4585Mar 8, 14:55failedamelia.king@example.com€637.00
#4584Mar 8, 12:30paidelijah.wright@example.com€784.00
#4583Mar 8, 09:45refundedharper.scott@example.com€345.00
#4582Mar 7, 23:10paidevelyn.green@example.com€918.00
#4581Mar 7, 20:25paidlogan.baker@example.com€567.00
0 of 0 row(s) selected.
Dieses Beispiel zeigt den häufigsten Anwendungsfall der Table-Komponente. Überprüfen Sie den Quellcode auf GitHub.

Data Bearbeiten

Verwenden Sie die data prop als ein Array von Objekten, die Spalten werden basierend auf den Schlüsseln der Objekte generiert.

IdDateStatusEmailAmount
46002024-03-11T15:30:00paidjames.anderson@example.com594
45992024-03-11T10:10:00failedmia.white@example.com276
45982024-03-11T08:50:00refundedwilliam.brown@example.com315
45972024-03-10T19:45:00paidemma.davis@example.com529
45962024-03-10T15:55:00paidethan.harris@example.com639
<script setup lang="ts">
const data = ref([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    status: 'paid',
    email: 'james.anderson@example.com',
    amount: 594
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    status: 'failed',
    email: 'mia.white@example.com',
    amount: 276
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    status: 'refunded',
    email: 'william.brown@example.com',
    amount: 315
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    status: 'paid',
    email: 'emma.davis@example.com',
    amount: 529
  },
  {
    id: '4596',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639
  }
])
</script>

<template>
  <UTable :data="data" class="flex-1" />
</template>
<script setup lang="ts">
import { ref } from 'vue'

const data = ref([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    status: 'paid',
    email: 'james.anderson@example.com',
    amount: 594
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    status: 'failed',
    email: 'mia.white@example.com',
    amount: 276
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    status: 'refunded',
    email: 'william.brown@example.com',
    amount: 315
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    status: 'paid',
    email: 'emma.davis@example.com',
    amount: 529
  },
  {
    id: '4596',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639
  }
])
</script>

<template>
  <UTable :data="data" class="flex-1" />
</template>

columns (englisch)

Verwenden Sie die columns-Prop als Array von ColumnDef-Objekten mit Eigenschaften wie

  • accessorKey:Der Schlüssel des Zeilenobjekts, das beim Extrahieren des Werts für die Spalte verwendet werden soll. {class="text-muted"}
  • header (auf Englisch):Der Header, der für die Spalte angezeigt werden soll. Wenn eine Zeichenfolge übergeben wird, kann sie als Standard für die Spalten-ID verwendet werden. Wenn eine Funktion übergeben wird, wird ein Props-Objekt für die Kopfzeile übergeben und sollte den gerenderten Header-Wert zurückgeben (der genaue Typ hängt vom verwendeten Adapter ab). {class="text-muted"}
  • footer:Die Fußzeile, die für die Spalte angezeigt werden soll. Funktioniert genau wie die Kopfzeile, wird aber unter der Tabelle angezeigt. {class="text-muted"}
  • cell (auf Englisch):Die Zelle, die jede Zeile für die Spalte anzeigen soll. Wenn eine Funktion übergeben wird, wird ihr ein Props-Objekt für die Zelle übergeben und sollte den gerenderten Zellwert zurückgeben (der genaue Typ hängt vom verwendeten Adapter ab). {class="text-muted"}
  • meta:Extra properties for the column. {class="text-muted"} Zusätzliche Eigenschaften für die Spalte.
    • class:(deutsch)
      • td:Klassen, die auf das td-Element angewendet werden sollen. {class="text-muted"}
      • th:Klassen, die auf das th-Element angewendet werden sollen. {class="text-muted"}
    • style (englisch):
      • td:Der Stil, der auf das td-Element angewendet werden soll. {class="text-muted"}
      • th:Der auf das th-Element anzuwendende Stil. {class="text-muted"}
    • colspan: xph10103xcolspan](#with-column-span): #with-column-span): xph10103xcolspan](#with-column-span): xph10102xcolspan](]():
      • td:Das colspan-Attribut, das auf das td-Element angewendet werden soll. {class="text-muted"}
    • rowspan: #with-column-span): xph1113xrowspan](](#with-column-span): xph1115x): xph11115x): xph11115x):
      • td:Das rowspan Attribut, das auf das td Element angewendet werden soll. {class="text-muted"}

Um Komponenten oder andere HTML-Elemente zu rendern, müssen Sie die Vue h function innerhalb der header und cell props. This verwenden unterscheidet sich von anderen Komponenten, die Slots verwenden, sondern ermöglicht mehr Flexibilität.

Sie können auch Slots verwenden, um die Kopfzeile und die Datenzellen der Tabelle anzupassen.
#DateStatusEmailAmount
#4600Mar 11, 15:30paidjames.anderson@example.com€594.00
#4599Mar 11, 10:10failedmia.white@example.com€276.00
#4598Mar 11, 08:50refundedwilliam.brown@example.com€315.00
#4597Mar 10, 19:45paidemma.davis@example.com€529.00
#4596Mar 10, 15:55paidethan.harris@example.com€639.00
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'

const UBadge = resolveComponent('UBadge')

type Payment = {
  id: string
  date: string
  status: 'paid' | 'failed' | 'refunded'
  email: string
  amount: number
}

const data = ref<Payment[]>([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    status: 'paid',
    email: 'james.anderson@example.com',
    amount: 594
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    status: 'failed',
    email: 'mia.white@example.com',
    amount: 276
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    status: 'refunded',
    email: 'william.brown@example.com',
    amount: 315
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    status: 'paid',
    email: 'emma.davis@example.com',
    amount: 529
  },
  {
    id: '4596',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639
  }
])

const columns: TableColumn<Payment>[] = [
  {
    accessorKey: 'id',
    header: '#',
    cell: ({ row }) => `#${row.getValue('id')}`
  },
  {
    accessorKey: 'date',
    header: 'Date',
    cell: ({ row }) => {
      return new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      })
    }
  },
  {
    accessorKey: 'status',
    header: 'Status',
    cell: ({ row }) => {
      const color = {
        paid: 'success' as const,
        failed: 'error' as const,
        refunded: 'neutral' as const
      }[row.getValue('status') as string]

      return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
        row.getValue('status')
      )
    }
  },
  {
    accessorKey: 'email',
    header: 'Email'
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(amount)
    }
  }
]
</script>

<template>
  <UTable :data="data" :columns="columns" class="flex-1" />
</template>
<script setup lang="ts">
import { h, resolveComponent, ref } from 'vue'
import type { TableColumn } from '@nuxt/ui'

const UBadge = resolveComponent('UBadge')

type Payment = {
  id: string
  date: string
  status: 'paid' | 'failed' | 'refunded'
  email: string
  amount: number
}

const data = ref<Payment[]>([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    status: 'paid',
    email: 'james.anderson@example.com',
    amount: 594
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    status: 'failed',
    email: 'mia.white@example.com',
    amount: 276
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    status: 'refunded',
    email: 'william.brown@example.com',
    amount: 315
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    status: 'paid',
    email: 'emma.davis@example.com',
    amount: 529
  },
  {
    id: '4596',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639
  }
])

const columns: TableColumn<Payment>[] = [
  {
    accessorKey: 'id',
    header: '#',
    cell: ({ row }) => `#${row.getValue('id')}`
  },
  {
    accessorKey: 'date',
    header: 'Date',
    cell: ({ row }) => {
      return new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      })
    }
  },
  {
    accessorKey: 'status',
    header: 'Status',
    cell: ({ row }) => {
      const color = {
        paid: 'success' as const,
        failed: 'error' as const,
        refunded: 'neutral' as const
      }[row.getValue('status') as string]

      return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
        row.getValue('status')
      )
    }
  },
  {
    accessorKey: 'email',
    header: 'Email'
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(amount)
    }
  }
]
</script>

<template>
  <UTable :data="data" :columns="columns" class="flex-1" />
</template>
Wenn Sie Komponenten mit h rendern, können Sie entweder die Funktion resolveComponent verwenden oder aus #components importieren.

Meta ist ein

Verwenden Sie die meta-Prop als Objekt (TableMeta), um Eigenschaften zu übergeben wie:

  • class (englisch):
    • tr:Die Klassen, die auf das tr-Element angewendet werden sollen. {class="text-muted"}
  • style (englisch)
    • tr:Der auf das tr-Element anzuwendende Stil. {class="text-muted"}
IDDateStatusEmailAmount
4600Mar 11, 15:30paidjames.anderson@example.com$594.00
4599Mar 11, 10:10failedmia.white@example.com$276.00
4598Mar 11, 08:50refundedwilliam.brown@example.com$315.00
4597Mar 10, 19:45paidemma.davis@example.com$529.00
4596Mar 10, 15:55paidethan.harris@example.com$639.00
<script setup lang="ts">
import type { TableColumn } from '@nuxt/ui'
import type { TableMeta, Row } from '@tanstack/vue-table'

type Payment = {
  id: string
  date: string
  status: 'paid' | 'failed' | 'refunded'
  email: string
  amount: number
}

const data = ref<Payment[]>([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    status: 'paid',
    email: 'james.anderson@example.com',
    amount: 594
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    status: 'failed',
    email: 'mia.white@example.com',
    amount: 276
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    status: 'refunded',
    email: 'william.brown@example.com',
    amount: 315
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    status: 'paid',
    email: 'emma.davis@example.com',
    amount: 529
  },
  {
    id: '4596',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639
  }
])

const columns: TableColumn<Payment>[] = [
  {
    accessorKey: 'id',
    header: 'ID',
    meta: {
      class: {
        th: 'text-center font-semibold',
        td: 'text-center font-mono'
      }
    }
  },
  {
    accessorKey: 'date',
    header: 'Date',
    cell: ({ row }) => {
      return new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      })
    }
  },
  {
    accessorKey: 'status',
    header: 'Status',
    meta: {
      class: {
        th: 'text-center',
        td: 'text-center'
      }
    },
    cell: ({ row }) => {
      const status = row.getValue('status') as string
      const colorMap = {
        paid: 'text-success',
        failed: 'text-error',
        refunded: 'text-warning'
      }
      return h(
        'span',
        { class: `font-semibold capitalize ${colorMap[status as keyof typeof colorMap]}` },
        status
      )
    }
  },
  {
    accessorKey: 'email',
    header: 'Email',
    meta: {
      class: {
        th: 'text-left',
        td: 'text-left'
      }
    }
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: {
      class: {
        th: 'text-right font-bold text-primary',
        td: 'text-right font-mono'
      }
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      const formatted = new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'USD'
      }).format(amount)
      return h('span', { class: 'font-semibold text-success' }, formatted)
    }
  }
]

const meta: TableMeta<Payment> = {
  class: {
    tr: (row: Row<Payment>) => {
      if (row.original.status === 'failed') {
        return 'bg-error/10'
      }
      if (row.original.status === 'refunded') {
        return 'bg-warning/10'
      }
      return ''
    }
  }
}
</script>

<template>
  <UTable :data="data" :columns="columns" :meta="meta" class="flex-1" />
</template>
<script setup lang="ts">
import { ref, h } from 'vue'
import type { TableColumn } from '@nuxt/ui'
import type { TableMeta, Row } from '@tanstack/vue-table'

type Payment = {
  id: string
  date: string
  status: 'paid' | 'failed' | 'refunded'
  email: string
  amount: number
}

const data = ref<Payment[]>([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    status: 'paid',
    email: 'james.anderson@example.com',
    amount: 594
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    status: 'failed',
    email: 'mia.white@example.com',
    amount: 276
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    status: 'refunded',
    email: 'william.brown@example.com',
    amount: 315
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    status: 'paid',
    email: 'emma.davis@example.com',
    amount: 529
  },
  {
    id: '4596',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639
  }
])

const columns: TableColumn<Payment>[] = [
  {
    accessorKey: 'id',
    header: 'ID',
    meta: {
      class: {
        th: 'text-center font-semibold',
        td: 'text-center font-mono'
      }
    }
  },
  {
    accessorKey: 'date',
    header: 'Date',
    cell: ({ row }) => {
      return new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      })
    }
  },
  {
    accessorKey: 'status',
    header: 'Status',
    meta: {
      class: {
        th: 'text-center',
        td: 'text-center'
      }
    },
    cell: ({ row }) => {
      const status = row.getValue('status') as string
      const colorMap = {
        paid: 'text-success',
        failed: 'text-error',
        refunded: 'text-warning'
      }
      return h(
        'span',
        { class: `font-semibold capitalize ${colorMap[status as keyof typeof colorMap]}` },
        status
      )
    }
  },
  {
    accessorKey: 'email',
    header: 'Email',
    meta: {
      class: {
        th: 'text-left',
        td: 'text-left'
      }
    }
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: {
      class: {
        th: 'text-right font-bold text-primary',
        td: 'text-right font-mono'
      }
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      const formatted = new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'USD'
      }).format(amount)
      return h('span', { class: 'font-semibold text-success' }, formatted)
    }
  }
]

const meta: TableMeta<Payment> = {
  class: {
    tr: (row: Row<Payment>) => {
      if (row.original.status === 'failed') {
        return 'bg-error/10'
      }
      if (row.original.status === 'refunded') {
        return 'bg-warning/10'
      }
      return ''
    }
  }
}
</script>

<template>
  <UTable :data="data" :columns="columns" :meta="meta" class="flex-1" />
</template>

loading (englisch)

Verwenden Sie die loading prop, um einen Ladezustand anzuzeigen, die loading-color prop, um ihre Farbe zu ändern, und die loading-animation prop, um ihre Animation zu ändern.

IdDateStatusEmailAmount
46002024-03-11T15:30:00paidjames.anderson@example.com594
45992024-03-11T10:10:00failedmia.white@example.com276
45982024-03-11T08:50:00refundedwilliam.brown@example.com315
45972024-03-10T19:45:00paidemma.davis@example.com529
45962024-03-10T15:55:00paidethan.harris@example.com639
<script setup lang="ts">
const data = ref([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    status: 'paid',
    email: 'james.anderson@example.com',
    amount: 594
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    status: 'failed',
    email: 'mia.white@example.com',
    amount: 276
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    status: 'refunded',
    email: 'william.brown@example.com',
    amount: 315
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    status: 'paid',
    email: 'emma.davis@example.com',
    amount: 529
  },
  {
    id: '4596',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639
  }
])
</script>

<template>
  <UTable
    loading
    loading-color="primary"
    loading-animation="carousel"
    :data="data"
    class="flex-1"
  />
</template>
<script setup lang="ts">
import { ref } from 'vue'

const data = ref([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    status: 'paid',
    email: 'james.anderson@example.com',
    amount: 594
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    status: 'failed',
    email: 'mia.white@example.com',
    amount: 276
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    status: 'refunded',
    email: 'william.brown@example.com',
    amount: 315
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    status: 'paid',
    email: 'emma.davis@example.com',
    amount: 529
  },
  {
    id: '4596',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639
  }
])
</script>

<template>
  <UTable
    loading
    loading-color="primary"
    loading-animation="carousel"
    :data="data"
    class="flex-1"
  />
</template>
Die Ladeanimation wird automatisch deaktiviert, wenn der Benutzer eine reduzierte Bewegung bevorzugt, stattdessen wird die Leiste als Impuls in voller Breite angezeigt.

Sticky Bearbeiten

Verwenden Sie die sticky prop, um die Kopf-oder Fußzeile klebrig zu machen.

IdDateStatusEmailAmount
46002024-03-11T15:30:00paidjames.anderson@example.com594
45992024-03-11T10:10:00failedmia.white@example.com276
45982024-03-11T08:50:00refundedwilliam.brown@example.com315
45972024-03-10T19:45:00paidemma.davis@example.com529
45962024-03-10T15:55:00paidethan.harris@example.com639
45952024-03-10T15:55:00paidethan.harris@example.com639
45942024-03-10T15:55:00paidethan.harris@example.com639
<script setup lang="ts">
const data = ref([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    status: 'paid',
    email: 'james.anderson@example.com',
    amount: 594
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    status: 'failed',
    email: 'mia.white@example.com',
    amount: 276
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    status: 'refunded',
    email: 'william.brown@example.com',
    amount: 315
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    status: 'paid',
    email: 'emma.davis@example.com',
    amount: 529
  },
  {
    id: '4596',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639
  },
  {
    id: '4595',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639
  },
  {
    id: '4594',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639
  }
])
</script>

<template>
  <UTable sticky :data="data" class="flex-1 max-h-[312px]" />
</template>
<script setup lang="ts">
import { ref } from 'vue'

const data = ref([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    status: 'paid',
    email: 'james.anderson@example.com',
    amount: 594
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    status: 'failed',
    email: 'mia.white@example.com',
    amount: 276
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    status: 'refunded',
    email: 'william.brown@example.com',
    amount: 315
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    status: 'paid',
    email: 'emma.davis@example.com',
    amount: 529
  },
  {
    id: '4596',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639
  },
  {
    id: '4595',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639
  },
  {
    id: '4594',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639
  }
])
</script>

<template>
  <UTable sticky :data="data" class="flex-1 max-h-[312px]" />
</template>

Beispiele

With Zeilenaktionen

Sie können eine neue Spalte hinzufügen, die eine DropdownMenu-Komponente in der cell rendert, um Zeilenaktionen zu rendern.

#DateStatusEmailAmount
#4600Mar 11, 15:30paidjames.anderson@example.com€594.00
#4599Mar 11, 10:10failedmia.white@example.com€276.00
#4598Mar 11, 08:50refundedwilliam.brown@example.com€315.00
#4597Mar 10, 19:45paidemma.davis@example.com€529.00
#4596Mar 10, 15:55paidethan.harris@example.com€639.00
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'
import type { Row } from '@tanstack/vue-table'
import { useClipboard } from '@vueuse/core'

const UButton = resolveComponent('UButton')
const UBadge = resolveComponent('UBadge')
const UDropdownMenu = resolveComponent('UDropdownMenu')

const toast = useToast()
const { copy } = useClipboard()

type Payment = {
  id: string
  date: string
  status: 'paid' | 'failed' | 'refunded'
  email: string
  amount: number
}

const data = ref<Payment[]>([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    status: 'paid',
    email: 'james.anderson@example.com',
    amount: 594
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    status: 'failed',
    email: 'mia.white@example.com',
    amount: 276
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    status: 'refunded',
    email: 'william.brown@example.com',
    amount: 315
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    status: 'paid',
    email: 'emma.davis@example.com',
    amount: 529
  },
  {
    id: '4596',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639
  }
])

const columns: TableColumn<Payment>[] = [
  {
    accessorKey: 'id',
    header: '#',
    cell: ({ row }) => `#${row.getValue('id')}`
  },
  {
    accessorKey: 'date',
    header: 'Date',
    cell: ({ row }) => {
      return new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      })
    }
  },
  {
    accessorKey: 'status',
    header: 'Status',
    cell: ({ row }) => {
      const color = {
        paid: 'success' as const,
        failed: 'error' as const,
        refunded: 'neutral' as const
      }[row.getValue('status') as string]

      return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
        row.getValue('status')
      )
    }
  },
  {
    accessorKey: 'email',
    header: 'Email'
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(amount)
    }
  },
  {
    id: 'actions',
    meta: {
      class: {
        td: 'text-right'
      }
    },
    cell: ({ row }) => {
      return h(
        UDropdownMenu,
        {
          content: {
            align: 'end'
          },
          items: getRowItems(row),
          'aria-label': 'Actions dropdown'
        },
        () =>
          h(UButton, {
            icon: 'i-lucide-ellipsis-vertical',
            color: 'neutral',
            variant: 'ghost',
            'aria-label': 'Actions dropdown'
          })
      )
    }
  }
]

function getRowItems(row: Row<Payment>) {
  return [
    {
      type: 'label',
      label: 'Actions'
    },
    {
      label: 'Copy payment ID',
      onSelect() {
        copy(row.original.id)

        toast.add({
          title: 'Payment ID copied to clipboard!',
          color: 'success',
          icon: 'i-lucide-circle-check'
        })
      }
    },
    {
      type: 'separator'
    },
    {
      label: 'View customer'
    },
    {
      label: 'View payment details'
    }
  ]
}
</script>

<template>
  <UTable :data="data" :columns="columns" class="flex-1" />
</template>
<script setup lang="ts">
import { h, resolveComponent, ref } from 'vue'
import type { TableColumn } from '@nuxt/ui'
import type { Row } from '@tanstack/vue-table'
import { useClipboard } from '@vueuse/core'

const UButton = resolveComponent('UButton')
const UBadge = resolveComponent('UBadge')
const UDropdownMenu = resolveComponent('UDropdownMenu')

const toast = useToast()
const { copy } = useClipboard()

type Payment = {
  id: string
  date: string
  status: 'paid' | 'failed' | 'refunded'
  email: string
  amount: number
}

const data = ref<Payment[]>([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    status: 'paid',
    email: 'james.anderson@example.com',
    amount: 594
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    status: 'failed',
    email: 'mia.white@example.com',
    amount: 276
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    status: 'refunded',
    email: 'william.brown@example.com',
    amount: 315
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    status: 'paid',
    email: 'emma.davis@example.com',
    amount: 529
  },
  {
    id: '4596',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639
  }
])

const columns: TableColumn<Payment>[] = [
  {
    accessorKey: 'id',
    header: '#',
    cell: ({ row }) => `#${row.getValue('id')}`
  },
  {
    accessorKey: 'date',
    header: 'Date',
    cell: ({ row }) => {
      return new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      })
    }
  },
  {
    accessorKey: 'status',
    header: 'Status',
    cell: ({ row }) => {
      const color = {
        paid: 'success' as const,
        failed: 'error' as const,
        refunded: 'neutral' as const
      }[row.getValue('status') as string]

      return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
        row.getValue('status')
      )
    }
  },
  {
    accessorKey: 'email',
    header: 'Email'
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(amount)
    }
  },
  {
    id: 'actions',
    meta: {
      class: {
        td: 'text-right'
      }
    },
    cell: ({ row }) => {
      return h(
        UDropdownMenu,
        {
          content: {
            align: 'end'
          },
          items: getRowItems(row),
          'aria-label': 'Actions dropdown'
        },
        () =>
          h(UButton, {
            icon: 'i-lucide-ellipsis-vertical',
            color: 'neutral',
            variant: 'ghost',
            'aria-label': 'Actions dropdown'
          })
      )
    }
  }
]

function getRowItems(row: Row<Payment>) {
  return [
    {
      type: 'label',
      label: 'Actions'
    },
    {
      label: 'Copy payment ID',
      onSelect() {
        copy(row.original.id)

        toast.add({
          title: 'Payment ID copied to clipboard!',
          color: 'success',
          icon: 'i-lucide-circle-check'
        })
      }
    },
    {
      type: 'separator'
    },
    {
      label: 'View customer'
    },
    {
      label: 'View payment details'
    }
  ]
}
</script>

<template>
  <UTable :data="data" :columns="columns" class="flex-1" />
</template>

With erweiterbare Zeilen

Sie können eine neue Spalte hinzufügen, die eine Button-Komponente innerhalb der cell rendert, um den erweiterbaren Status einer Zeile mithilfe der TanStack-Tabelle Expanding APIs umzuschalten.

Sie müssen den #expanded-slot definieren, um den erweiterten inhalt zu rendern, der die zeile als parameter erhält.
#DateStatusEmailAmount
#4600Mar 11, 15:30paidjames.anderson@example.com€594.00
#4599Mar 11, 10:10failedmia.white@example.com€276.00
{
  "id": "4599",
  "date": "2024-03-11T10:10:00",
  "status": "failed",
  "email": "mia.white@example.com",
  "amount": 276
}
#4598Mar 11, 08:50refundedwilliam.brown@example.com€315.00
#4597Mar 10, 19:45paidemma.davis@example.com€529.00
#4596Mar 10, 15:55paidethan.harris@example.com€639.00
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'

const UButton = resolveComponent('UButton')
const UBadge = resolveComponent('UBadge')

type Payment = {
  id: string
  date: string
  status: 'paid' | 'failed' | 'refunded'
  email: string
  amount: number
}

const data = ref<Payment[]>([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    status: 'paid',
    email: 'james.anderson@example.com',
    amount: 594
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    status: 'failed',
    email: 'mia.white@example.com',
    amount: 276
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    status: 'refunded',
    email: 'william.brown@example.com',
    amount: 315
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    status: 'paid',
    email: 'emma.davis@example.com',
    amount: 529
  },
  {
    id: '4596',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639
  }
])

const columns: TableColumn<Payment>[] = [
  {
    id: 'expand',
    cell: ({ row }) =>
      h(UButton, {
        color: 'neutral',
        variant: 'ghost',
        icon: 'i-lucide-chevron-down',
        square: true,
        'aria-label': 'Expand',
        ui: {
          leadingIcon: [
            'transition-transform',
            row.getIsExpanded() ? 'duration-200 rotate-180' : ''
          ]
        },
        onClick: () => row.toggleExpanded()
      })
  },
  {
    accessorKey: 'id',
    header: '#',
    cell: ({ row }) => `#${row.getValue('id')}`
  },
  {
    accessorKey: 'date',
    header: 'Date',
    cell: ({ row }) => {
      return new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      })
    }
  },
  {
    accessorKey: 'status',
    header: 'Status',
    cell: ({ row }) => {
      const color = {
        paid: 'success' as const,
        failed: 'error' as const,
        refunded: 'neutral' as const
      }[row.getValue('status') as string]

      return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
        row.getValue('status')
      )
    }
  },
  {
    accessorKey: 'email',
    header: 'Email'
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(amount)
    }
  }
]

const expanded = ref({ 1: true })
</script>

<template>
  <UTable
    v-model:expanded="expanded"
    :data="data"
    :columns="columns"
    :ui="{ tr: 'data-[expanded=true]:bg-elevated/50' }"
    class="flex-1"
  >
    <template #expanded="{ row }">
      <pre>{{ row.original }}</pre>
    </template>
  </UTable>
</template>
<script setup lang="ts">
import { h, resolveComponent, ref } from 'vue'
import type { TableColumn } from '@nuxt/ui'

const UButton = resolveComponent('UButton')
const UBadge = resolveComponent('UBadge')

type Payment = {
  id: string
  date: string
  status: 'paid' | 'failed' | 'refunded'
  email: string
  amount: number
}

const data = ref<Payment[]>([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    status: 'paid',
    email: 'james.anderson@example.com',
    amount: 594
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    status: 'failed',
    email: 'mia.white@example.com',
    amount: 276
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    status: 'refunded',
    email: 'william.brown@example.com',
    amount: 315
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    status: 'paid',
    email: 'emma.davis@example.com',
    amount: 529
  },
  {
    id: '4596',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639
  }
])

const columns: TableColumn<Payment>[] = [
  {
    id: 'expand',
    cell: ({ row }) =>
      h(UButton, {
        color: 'neutral',
        variant: 'ghost',
        icon: 'i-lucide-chevron-down',
        square: true,
        'aria-label': 'Expand',
        ui: {
          leadingIcon: [
            'transition-transform',
            row.getIsExpanded() ? 'duration-200 rotate-180' : ''
          ]
        },
        onClick: () => row.toggleExpanded()
      })
  },
  {
    accessorKey: 'id',
    header: '#',
    cell: ({ row }) => `#${row.getValue('id')}`
  },
  {
    accessorKey: 'date',
    header: 'Date',
    cell: ({ row }) => {
      return new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      })
    }
  },
  {
    accessorKey: 'status',
    header: 'Status',
    cell: ({ row }) => {
      const color = {
        paid: 'success' as const,
        failed: 'error' as const,
        refunded: 'neutral' as const
      }[row.getValue('status') as string]

      return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
        row.getValue('status')
      )
    }
  },
  {
    accessorKey: 'email',
    header: 'Email'
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(amount)
    }
  }
]

const expanded = ref({ 1: true })
</script>

<template>
  <UTable
    v-model:expanded="expanded"
    :data="data"
    :columns="columns"
    :ui="{ tr: 'data-[expanded=true]:bg-elevated/50' }"
    class="flex-1"
  >
    <template #expanded="{ row }">
      <pre>{{ row.original }}</pre>
    </template>
  </UTable>
</template>
Sie können die expanded-prop verwenden, um den erweiterbaren Zustand der Zeilen zu steuern (kann mit v-model gebunden werden).
Sie können diese Aktion auch der DropdownMenu-Komponente in der actions-Spalte hinzufügen.

Mit gruppierten Zeilen

Sie können Zeilen basierend auf einem bestimmten Spaltenwert gruppieren und Unterzeilen über eine Schaltfläche ein-und ausblenden, die der Zelle mithilfe der TanStack-Tabelle Grouping APIs hinzugefügt wurde.

Wichtige Teile

  • Fügen Sie grouping prop mit einem Array von Spalten-IDs hinzu, nach denen Sie gruppieren möchten.
  • Add grouping-options prop. Es muss getGroupedRowModel enthalten, Sie können es von @tanstack/vue-table importieren oder Ihr eigenes implementieren.
  • Expand rows via row.toggleExpanded() method on any cell of the row. Denken Sie daran, dass es auch den #expanded-Slot schaltet.
  • Verwenden Sie aggregateFn für die Spaltendefinition, um zu definieren, wie die Zeilen aggregiert werden sollen. Der * agregatedCell-Renderer bei Spaltendefinition funktioniert nur, wenn es keinen cell-Renderer gibt.
Item#DateEmailAmount
Account 1
3 recordsMar 11, 15:303 customers€1,548.00
Account 2
2 recordsMar 11, 10:102 customers€805.00
<script setup lang="ts">
import { resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'
import { getGroupedRowModel } from '@tanstack/vue-table'
import type { GroupingOptions } from '@tanstack/vue-table'

const UBadge = resolveComponent('UBadge')

type Account = {
  id: string
  name: string
}

type PaymentStatus = 'paid' | 'failed' | 'refunded'

type Payment = {
  id: string
  date: string
  status: PaymentStatus
  email: string
  amount: number
  account: Account
}

const getColorByStatus = (status: PaymentStatus) => {
  return {
    paid: 'success',
    failed: 'error',
    refunded: 'neutral'
  }[status]
}

const data = ref<Payment[]>([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    status: 'paid',
    email: 'james.anderson@example.com',
    amount: 594,
    account: {
      id: '1',
      name: 'Account 1'
    }
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    status: 'failed',
    email: 'mia.white@example.com',
    amount: 276,
    account: {
      id: '2',
      name: 'Account 2'
    }
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    status: 'refunded',
    email: 'william.brown@example.com',
    amount: 315,
    account: {
      id: '1',
      name: 'Account 1'
    }
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    status: 'paid',
    email: 'emma.davis@example.com',
    amount: 529,
    account: {
      id: '2',
      name: 'Account 2'
    }
  },
  {
    id: '4596',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639,
    account: {
      id: '1',
      name: 'Account 1'
    }
  }
])

const columns: TableColumn<Payment>[] = [
  {
    id: 'title',
    header: 'Item'
  },
  {
    id: 'account_id',
    accessorKey: 'account.id'
  },
  {
    accessorKey: 'id',
    header: '#',
    cell: ({ row }) =>
      row.getIsGrouped() ? `${row.getValue('id')} records` : `#${row.getValue('id')}`,
    aggregationFn: 'count'
  },
  {
    accessorKey: 'date',
    header: 'Date',
    cell: ({ row }) => {
      return new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      })
    },
    aggregationFn: 'max'
  },
  {
    accessorKey: 'status',
    header: 'Status'
  },
  {
    accessorKey: 'email',
    header: 'Email',
    meta: {
      class: {
        td: 'w-full'
      }
    },
    cell: ({ row }) =>
      row.getIsGrouped() ? `${row.getValue('email')} customers` : row.getValue('email'),
    aggregationFn: 'uniqueCount'
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(amount)
    },
    aggregationFn: 'sum'
  }
]

const grouping_options = ref<GroupingOptions>({
  groupedColumnMode: 'remove',
  getGroupedRowModel: getGroupedRowModel()
})
</script>

<template>
  <UTable
    :data="data"
    :columns="columns"
    :grouping="['account_id', 'status']"
    :grouping-options="grouping_options"
    :ui="{
      root: 'min-w-full',
      td: 'empty:p-0' // helps with the colspaned row added for expand slot
    }"
  >
    <template #title-cell="{ row }">
      <div v-if="row.getIsGrouped()" class="flex items-center">
        <span class="inline-block" :style="{ width: `calc(${row.depth} * 1rem)` }" />

        <UButton
          variant="outline"
          color="neutral"
          class="mr-2"
          size="xs"
          :icon="row.getIsExpanded() ? 'i-lucide-minus' : 'i-lucide-plus'"
          @click="row.toggleExpanded()"
        />
        <strong v-if="row.groupingColumnId === 'account_id'">{{
          row.original.account.name
        }}</strong>
        <UBadge
          v-else-if="row.groupingColumnId === 'status'"
          :color="getColorByStatus(row.original.status)"
          class="capitalize"
          variant="subtle"
        >
          {{ row.original.status }}
        </UBadge>
      </div>
    </template>
  </UTable>
</template>
<script setup lang="ts">
import { resolveComponent, ref } from 'vue'
import type { TableColumn } from '@nuxt/ui'
import { getGroupedRowModel } from '@tanstack/vue-table'
import type { GroupingOptions } from '@tanstack/vue-table'

const UBadge = resolveComponent('UBadge')

type Account = {
  id: string
  name: string
}

type PaymentStatus = 'paid' | 'failed' | 'refunded'

type Payment = {
  id: string
  date: string
  status: PaymentStatus
  email: string
  amount: number
  account: Account
}

const getColorByStatus = (status: PaymentStatus) => {
  return {
    paid: 'success',
    failed: 'error',
    refunded: 'neutral'
  }[status]
}

const data = ref<Payment[]>([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    status: 'paid',
    email: 'james.anderson@example.com',
    amount: 594,
    account: {
      id: '1',
      name: 'Account 1'
    }
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    status: 'failed',
    email: 'mia.white@example.com',
    amount: 276,
    account: {
      id: '2',
      name: 'Account 2'
    }
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    status: 'refunded',
    email: 'william.brown@example.com',
    amount: 315,
    account: {
      id: '1',
      name: 'Account 1'
    }
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    status: 'paid',
    email: 'emma.davis@example.com',
    amount: 529,
    account: {
      id: '2',
      name: 'Account 2'
    }
  },
  {
    id: '4596',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639,
    account: {
      id: '1',
      name: 'Account 1'
    }
  }
])

const columns: TableColumn<Payment>[] = [
  {
    id: 'title',
    header: 'Item'
  },
  {
    id: 'account_id',
    accessorKey: 'account.id'
  },
  {
    accessorKey: 'id',
    header: '#',
    cell: ({ row }) =>
      row.getIsGrouped() ? `${row.getValue('id')} records` : `#${row.getValue('id')}`,
    aggregationFn: 'count'
  },
  {
    accessorKey: 'date',
    header: 'Date',
    cell: ({ row }) => {
      return new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      })
    },
    aggregationFn: 'max'
  },
  {
    accessorKey: 'status',
    header: 'Status'
  },
  {
    accessorKey: 'email',
    header: 'Email',
    meta: {
      class: {
        td: 'w-full'
      }
    },
    cell: ({ row }) =>
      row.getIsGrouped() ? `${row.getValue('email')} customers` : row.getValue('email'),
    aggregationFn: 'uniqueCount'
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(amount)
    },
    aggregationFn: 'sum'
  }
]

const grouping_options = ref<GroupingOptions>({
  groupedColumnMode: 'remove',
  getGroupedRowModel: getGroupedRowModel()
})
</script>

<template>
  <UTable
    :data="data"
    :columns="columns"
    :grouping="['account_id', 'status']"
    :grouping-options="grouping_options"
    :ui="{
      root: 'min-w-full',
      td: 'empty:p-0' // helps with the colspaned row added for expand slot
    }"
  >
    <template #title-cell="{ row }">
      <div v-if="row.getIsGrouped()" class="flex items-center">
        <span class="inline-block" :style="{ width: `calc(${row.depth} * 1rem)` }" />

        <UButton
          variant="outline"
          color="neutral"
          class="mr-2"
          size="xs"
          :icon="row.getIsExpanded() ? 'i-lucide-minus' : 'i-lucide-plus'"
          @click="row.toggleExpanded()"
        />
        <strong v-if="row.groupingColumnId === 'account_id'">{{
          row.original.account.name
        }}</strong>
        <UBadge
          v-else-if="row.groupingColumnId === 'status'"
          :color="getColorByStatus(row.original.status)"
          class="capitalize"
          variant="subtle"
        >
          {{ row.original.status }}
        </UBadge>
      </div>
    </template>
  </UTable>
</template>

With Zeilenanheftung: badge{label="4.6+" class="align-text-top"}

Sie können eine Spalte hinzufügen, die eine Button-Komponente innerhalb der cell rendert, um den Pinning-Status einer Zeile mithilfe der TanStack-Tabelle Row Pinning APIs umzuschalten.

DateStatusEmailAmount
Mar 11, 10:10failedmia.white@example.com€276.00
Mar 10, 19:45paidemma.davis@example.com€529.00
Mar 11, 15:30paidjames.anderson@example.com€594.00
Mar 11, 08:50refundedwilliam.brown@example.com€315.00
Mar 10, 15:55paidethan.harris@example.com€639.00
Mar 10, 13:40refundedava.thomas@example.com€428.00
Mar 10, 09:15paidmichael.wilson@example.com€683.00
Mar 9, 20:25failedolivia.taylor@example.com€947.00
Mar 9, 18:45paidbenjamin.jackson@example.com€851.00
Mar 9, 16:05paidsophia.miller@example.com€762.00
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'
import type { RowPinningState } from '@tanstack/table-core'

const UButton = resolveComponent('UButton')
const UBadge = resolveComponent('UBadge')

type Payment = {
  id: string
  date: string
  status: 'paid' | 'failed' | 'refunded'
  email: string
  amount: number
}

const data = ref<Payment[]>([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    status: 'paid',
    email: 'james.anderson@example.com',
    amount: 594
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    status: 'failed',
    email: 'mia.white@example.com',
    amount: 276
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    status: 'refunded',
    email: 'william.brown@example.com',
    amount: 315
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    status: 'paid',
    email: 'emma.davis@example.com',
    amount: 529
  },
  {
    id: '4596',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639
  },
  {
    id: '4595',
    date: '2024-03-10T13:40:00',
    status: 'refunded',
    email: 'ava.thomas@example.com',
    amount: 428
  },
  {
    id: '4594',
    date: '2024-03-10T09:15:00',
    status: 'paid',
    email: 'michael.wilson@example.com',
    amount: 683
  },
  {
    id: '4593',
    date: '2024-03-09T20:25:00',
    status: 'failed',
    email: 'olivia.taylor@example.com',
    amount: 947
  },
  {
    id: '4592',
    date: '2024-03-09T18:45:00',
    status: 'paid',
    email: 'benjamin.jackson@example.com',
    amount: 851
  },
  {
    id: '4591',
    date: '2024-03-09T16:05:00',
    status: 'paid',
    email: 'sophia.miller@example.com',
    amount: 762
  }
])

const columns: TableColumn<Payment>[] = [
  {
    id: 'pin',
    cell: ({ row }) =>
      h(UButton, {
        icon: 'i-lucide-star',
        color: row.getIsPinned() ? 'primary' : 'neutral',
        variant: 'ghost',
        'aria-label': row.getIsPinned() ? 'Unpin row' : 'Pin row to top',
        onClick: () => {
          if (row.getIsPinned()) {
            row.pin(false)
          } else {
            row.pin('top')
          }
        }
      })
  },
  {
    accessorKey: 'date',
    header: 'Date',
    cell: ({ row }) => {
      return new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false,
        timeZone: 'UTC'
      })
    }
  },
  {
    accessorKey: 'status',
    header: 'Status',
    cell: ({ row }) => {
      const color = {
        paid: 'success' as const,
        failed: 'error' as const,
        refunded: 'neutral' as const
      }[row.getValue('status') as string]

      return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
        row.getValue('status')
      )
    }
  },
  {
    accessorKey: 'email',
    header: 'Email'
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(amount)
    }
  }
]

const rowPinning = ref<RowPinningState>({ top: ['4599', '4597'], bottom: [] })
</script>

<template>
  <UTable
    v-model:row-pinning="rowPinning"
    :data="data"
    :columns="columns"
    :get-row-id="(row: Payment) => row.id"
    class="flex-1 h-96"
  />
</template>
<script setup lang="ts">
import { h, resolveComponent, ref } from 'vue'
import type { TableColumn } from '@nuxt/ui'
import type { RowPinningState } from '@tanstack/table-core'

const UButton = resolveComponent('UButton')
const UBadge = resolveComponent('UBadge')

type Payment = {
  id: string
  date: string
  status: 'paid' | 'failed' | 'refunded'
  email: string
  amount: number
}

const data = ref<Payment[]>([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    status: 'paid',
    email: 'james.anderson@example.com',
    amount: 594
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    status: 'failed',
    email: 'mia.white@example.com',
    amount: 276
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    status: 'refunded',
    email: 'william.brown@example.com',
    amount: 315
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    status: 'paid',
    email: 'emma.davis@example.com',
    amount: 529
  },
  {
    id: '4596',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639
  },
  {
    id: '4595',
    date: '2024-03-10T13:40:00',
    status: 'refunded',
    email: 'ava.thomas@example.com',
    amount: 428
  },
  {
    id: '4594',
    date: '2024-03-10T09:15:00',
    status: 'paid',
    email: 'michael.wilson@example.com',
    amount: 683
  },
  {
    id: '4593',
    date: '2024-03-09T20:25:00',
    status: 'failed',
    email: 'olivia.taylor@example.com',
    amount: 947
  },
  {
    id: '4592',
    date: '2024-03-09T18:45:00',
    status: 'paid',
    email: 'benjamin.jackson@example.com',
    amount: 851
  },
  {
    id: '4591',
    date: '2024-03-09T16:05:00',
    status: 'paid',
    email: 'sophia.miller@example.com',
    amount: 762
  }
])

const columns: TableColumn<Payment>[] = [
  {
    id: 'pin',
    cell: ({ row }) =>
      h(UButton, {
        icon: 'i-lucide-star',
        color: row.getIsPinned() ? 'primary' : 'neutral',
        variant: 'ghost',
        'aria-label': row.getIsPinned() ? 'Unpin row' : 'Pin row to top',
        onClick: () => {
          if (row.getIsPinned()) {
            row.pin(false)
          } else {
            row.pin('top')
          }
        }
      })
  },
  {
    accessorKey: 'date',
    header: 'Date',
    cell: ({ row }) => {
      return new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false,
        timeZone: 'UTC'
      })
    }
  },
  {
    accessorKey: 'status',
    header: 'Status',
    cell: ({ row }) => {
      const color = {
        paid: 'success' as const,
        failed: 'error' as const,
        refunded: 'neutral' as const
      }[row.getValue('status') as string]

      return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
        row.getValue('status')
      )
    }
  },
  {
    accessorKey: 'email',
    header: 'Email'
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(amount)
    }
  }
]

const rowPinning = ref<RowPinningState>({ top: ['4599', '4597'], bottom: [] })
</script>

<template>
  <UTable
    v-model:row-pinning="rowPinning"
    :data="data"
    :columns="columns"
    :get-row-id="(row: Payment) => row.id"
    class="flex-1 h-96"
  />
</template>
Sie können die row-pinning-prop verwenden, um den Pinning-Zustand der Zeilen zu steuern (kann mit v-model gebunden werden).

With Zeilenauswahl

Sie können eine neue Spalte hinzufügen, die eine Checkbox-Komponente innerhalb der header und cell rendert, um Zeilen mit der TanStack-Tabelle Row Selection APIs auszuwählen.

DateStatusEmailAmount
Mar 11, 15:30paidjames.anderson@example.com€594.00
Mar 11, 10:10failedmia.white@example.com€276.00
Mar 11, 08:50refundedwilliam.brown@example.com€315.00
Mar 10, 19:45paidemma.davis@example.com€529.00
Mar 10, 15:55paidethan.harris@example.com€639.00
0 of 0 row(s) selected.
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'

const UCheckbox = resolveComponent('UCheckbox')
const UBadge = resolveComponent('UBadge')

type Payment = {
  id: string
  date: string
  status: 'paid' | 'failed' | 'refunded'
  email: string
  amount: number
}

const data = ref<Payment[]>([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    status: 'paid',
    email: 'james.anderson@example.com',
    amount: 594
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    status: 'failed',
    email: 'mia.white@example.com',
    amount: 276
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    status: 'refunded',
    email: 'william.brown@example.com',
    amount: 315
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    status: 'paid',
    email: 'emma.davis@example.com',
    amount: 529
  },
  {
    id: '4596',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639
  }
])

const columns: TableColumn<Payment>[] = [
  {
    id: 'select',
    header: ({ table }) =>
      h(UCheckbox, {
        modelValue: table.getIsSomePageRowsSelected()
          ? 'indeterminate'
          : table.getIsAllPageRowsSelected(),
        'onUpdate:modelValue': (value: boolean | 'indeterminate') =>
          table.toggleAllPageRowsSelected(!!value),
        'aria-label': 'Select all'
      }),
    cell: ({ row }) =>
      h(UCheckbox, {
        modelValue: row.getIsSelected(),
        'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
        'aria-label': 'Select row'
      })
  },
  {
    accessorKey: 'date',
    header: 'Date',
    cell: ({ row }) => {
      return new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      })
    }
  },
  {
    accessorKey: 'status',
    header: 'Status',
    cell: ({ row }) => {
      const color = {
        paid: 'success' as const,
        failed: 'error' as const,
        refunded: 'neutral' as const
      }[row.getValue('status') as string]

      return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
        row.getValue('status')
      )
    }
  },
  {
    accessorKey: 'email',
    header: 'Email'
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(amount)
    }
  }
]

const table = useTemplateRef('table')

const rowSelection = ref({ 1: true })
</script>

<template>
  <div class="flex-1 w-full">
    <UTable ref="table" v-model:row-selection="rowSelection" :data="data" :columns="columns" />

    <div class="px-4 py-3.5 border-t border-accented text-sm text-muted">
      {{ table?.tableApi?.getFilteredSelectedRowModel().rows.length || 0 }} of
      {{ table?.tableApi?.getFilteredRowModel().rows.length || 0 }} row(s) selected.
    </div>
  </div>
</template>
<script setup lang="ts">
import { h, resolveComponent, ref, useTemplateRef } from 'vue'
import type { TableColumn } from '@nuxt/ui'

const UCheckbox = resolveComponent('UCheckbox')
const UBadge = resolveComponent('UBadge')

type Payment = {
  id: string
  date: string
  status: 'paid' | 'failed' | 'refunded'
  email: string
  amount: number
}

const data = ref<Payment[]>([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    status: 'paid',
    email: 'james.anderson@example.com',
    amount: 594
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    status: 'failed',
    email: 'mia.white@example.com',
    amount: 276
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    status: 'refunded',
    email: 'william.brown@example.com',
    amount: 315
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    status: 'paid',
    email: 'emma.davis@example.com',
    amount: 529
  },
  {
    id: '4596',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639
  }
])

const columns: TableColumn<Payment>[] = [
  {
    id: 'select',
    header: ({ table }) =>
      h(UCheckbox, {
        modelValue: table.getIsSomePageRowsSelected()
          ? 'indeterminate'
          : table.getIsAllPageRowsSelected(),
        'onUpdate:modelValue': (value: boolean | 'indeterminate') =>
          table.toggleAllPageRowsSelected(!!value),
        'aria-label': 'Select all'
      }),
    cell: ({ row }) =>
      h(UCheckbox, {
        modelValue: row.getIsSelected(),
        'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
        'aria-label': 'Select row'
      })
  },
  {
    accessorKey: 'date',
    header: 'Date',
    cell: ({ row }) => {
      return new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      })
    }
  },
  {
    accessorKey: 'status',
    header: 'Status',
    cell: ({ row }) => {
      const color = {
        paid: 'success' as const,
        failed: 'error' as const,
        refunded: 'neutral' as const
      }[row.getValue('status') as string]

      return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
        row.getValue('status')
      )
    }
  },
  {
    accessorKey: 'email',
    header: 'Email'
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(amount)
    }
  }
]

const table = useTemplateRef('table')

const rowSelection = ref({ 1: true })
</script>

<template>
  <div class="flex-1 w-full">
    <UTable ref="table" v-model:row-selection="rowSelection" :data="data" :columns="columns" />

    <div class="px-4 py-3.5 border-t border-accented text-sm text-muted">
      {{ table?.tableApi?.getFilteredSelectedRowModel().rows.length || 0 }} of
      {{ table?.tableApi?.getFilteredRowModel().rows.length || 0 }} row(s) selected.
    </div>
  </div>
</template>
Sie können die row-selection-prop verwenden, um den Selektionsstatus der Zeilen zu steuern (kann mit v-model gebunden werden).

With row select event (mit Zeilenauswahl-Ereignis)

Sie können einen @select-Listener hinzufügen, um Zeilen mit oder ohne Checkbox-Spalte anklickbar zu machen.

Die handler-Funktion empfängt die Event-und TableRow-Instanz als erstes bzw. zweites Argument.
DateStatusEmailAmount
Mar 11, 15:30paidjames.anderson@example.com€594.00
Mar 11, 10:10failedmia.white@example.com€276.00
Mar 11, 08:50refundedwilliam.brown@example.com€315.00
Mar 10, 19:45paidemma.davis@example.com€529.00
Mar 10, 15:55paidethan.harris@example.com€639.00
0 of 0 row(s) selected.
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn, TableRow } from '@nuxt/ui'

const UBadge = resolveComponent('UBadge')
const UCheckbox = resolveComponent('UCheckbox')

type Payment = {
  id: string
  date: string
  status: 'paid' | 'failed' | 'refunded'
  email: string
  amount: number
}

const data = ref<Payment[]>([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    status: 'paid',
    email: 'james.anderson@example.com',
    amount: 594
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    status: 'failed',
    email: 'mia.white@example.com',
    amount: 276
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    status: 'refunded',
    email: 'william.brown@example.com',
    amount: 315
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    status: 'paid',
    email: 'emma.davis@example.com',
    amount: 529
  },
  {
    id: '4596',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639
  }
])

const columns: TableColumn<Payment>[] = [
  {
    id: 'select',
    header: ({ table }) =>
      h(UCheckbox, {
        modelValue: table.getIsSomePageRowsSelected()
          ? 'indeterminate'
          : table.getIsAllPageRowsSelected(),
        'onUpdate:modelValue': (value: boolean | 'indeterminate') =>
          table.toggleAllPageRowsSelected(!!value),
        'aria-label': 'Select all'
      }),
    cell: ({ row }) =>
      h(UCheckbox, {
        modelValue: row.getIsSelected(),
        'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
        'aria-label': 'Select row'
      })
  },
  {
    accessorKey: 'date',
    header: 'Date',
    cell: ({ row }) => {
      return new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      })
    }
  },
  {
    accessorKey: 'status',
    header: 'Status',
    cell: ({ row }) => {
      const color = {
        paid: 'success' as const,
        failed: 'error' as const,
        refunded: 'neutral' as const
      }[row.getValue('status') as string]

      return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
        row.getValue('status')
      )
    }
  },
  {
    accessorKey: 'email',
    header: 'Email'
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(amount)
    }
  }
]

const table = useTemplateRef('table')

const rowSelection = ref<Record<string, boolean>>({})

function onSelect(e: Event, row: TableRow<Payment>) {
  /* If you decide to also select the column you can do this  */
  row.toggleSelected(!row.getIsSelected())
}
</script>

<template>
  <div class="flex w-full flex-1 gap-1">
    <div class="flex-1">
      <UTable
        ref="table"
        v-model:row-selection="rowSelection"
        :data="data"
        :columns="columns"
        @select="onSelect"
      />

      <div class="px-4 py-3.5 border-t border-accented text-sm text-muted">
        {{ table?.tableApi?.getFilteredSelectedRowModel().rows.length || 0 }} of
        {{ table?.tableApi?.getFilteredRowModel().rows.length || 0 }} row(s) selected.
      </div>
    </div>
  </div>
</template>
<script setup lang="ts">
import { h, resolveComponent, ref, useTemplateRef } from 'vue'
import type { TableColumn, TableRow } from '@nuxt/ui'

const UBadge = resolveComponent('UBadge')
const UCheckbox = resolveComponent('UCheckbox')

type Payment = {
  id: string
  date: string
  status: 'paid' | 'failed' | 'refunded'
  email: string
  amount: number
}

const data = ref<Payment[]>([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    status: 'paid',
    email: 'james.anderson@example.com',
    amount: 594
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    status: 'failed',
    email: 'mia.white@example.com',
    amount: 276
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    status: 'refunded',
    email: 'william.brown@example.com',
    amount: 315
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    status: 'paid',
    email: 'emma.davis@example.com',
    amount: 529
  },
  {
    id: '4596',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639
  }
])

const columns: TableColumn<Payment>[] = [
  {
    id: 'select',
    header: ({ table }) =>
      h(UCheckbox, {
        modelValue: table.getIsSomePageRowsSelected()
          ? 'indeterminate'
          : table.getIsAllPageRowsSelected(),
        'onUpdate:modelValue': (value: boolean | 'indeterminate') =>
          table.toggleAllPageRowsSelected(!!value),
        'aria-label': 'Select all'
      }),
    cell: ({ row }) =>
      h(UCheckbox, {
        modelValue: row.getIsSelected(),
        'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
        'aria-label': 'Select row'
      })
  },
  {
    accessorKey: 'date',
    header: 'Date',
    cell: ({ row }) => {
      return new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      })
    }
  },
  {
    accessorKey: 'status',
    header: 'Status',
    cell: ({ row }) => {
      const color = {
        paid: 'success' as const,
        failed: 'error' as const,
        refunded: 'neutral' as const
      }[row.getValue('status') as string]

      return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
        row.getValue('status')
      )
    }
  },
  {
    accessorKey: 'email',
    header: 'Email'
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(amount)
    }
  }
]

const table = useTemplateRef('table')

const rowSelection = ref<Record<string, boolean>>({})

function onSelect(e: Event, row: TableRow<Payment>) {
  /* If you decide to also select the column you can do this  */
  row.toggleSelected(!row.getIsSelected())
}
</script>

<template>
  <div class="flex w-full flex-1 gap-1">
    <div class="flex-1">
      <UTable
        ref="table"
        v-model:row-selection="rowSelection"
        :data="data"
        :columns="columns"
        @select="onSelect"
      />

      <div class="px-4 py-3.5 border-t border-accented text-sm text-muted">
        {{ table?.tableApi?.getFilteredSelectedRowModel().rows.length || 0 }} of
        {{ table?.tableApi?.getFilteredRowModel().rows.length || 0 }} row(s) selected.
      </div>
    </div>
  </div>
</template>
Sie können damit zu einer Seite navigieren, ein Modal öffnen oder die Zeile sogar manuell auswählen.

With row context menu Veranstaltung

Sie können einen @contextmenu-Listener hinzufügen, um Zeilen mit der rechten Maustaste anklickbar zu machen, und die Tabelle in eine ContextMenu-Komponente einwickeln, um Zeilenaktionen anzuzeigen.

Die handler-Funktion empfängt die Event-und TableRow-Instanz als erstes bzw. zweites Argument.
#DateStatusEmailAmount
#4600Mar 11, 15:30paidjames.anderson@example.com€594.00
#4599Mar 11, 10:10failedmia.white@example.com€276.00
#4598Mar 11, 08:50refundedwilliam.brown@example.com€315.00
#4597Mar 10, 19:45paidemma.davis@example.com€529.00
#4596Mar 10, 15:55paidethan.harris@example.com€639.00
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { ContextMenuItem, TableColumn, TableRow } from '@nuxt/ui'
import { useClipboard } from '@vueuse/core'

const UBadge = resolveComponent('UBadge')
const UCheckbox = resolveComponent('UCheckbox')

const toast = useToast()
const { copy } = useClipboard()

type Payment = {
  id: string
  date: string
  status: 'paid' | 'failed' | 'refunded'
  email: string
  amount: number
}

const data = ref<Payment[]>([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    status: 'paid',
    email: 'james.anderson@example.com',
    amount: 594
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    status: 'failed',
    email: 'mia.white@example.com',
    amount: 276
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    status: 'refunded',
    email: 'william.brown@example.com',
    amount: 315
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    status: 'paid',
    email: 'emma.davis@example.com',
    amount: 529
  },
  {
    id: '4596',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639
  }
])

const columns: TableColumn<Payment>[] = [
  {
    id: 'select',
    header: ({ table }) =>
      h(UCheckbox, {
        modelValue: table.getIsSomePageRowsSelected()
          ? 'indeterminate'
          : table.getIsAllPageRowsSelected(),
        'onUpdate:modelValue': (value: boolean | 'indeterminate') =>
          table.toggleAllPageRowsSelected(!!value),
        'aria-label': 'Select all'
      }),
    cell: ({ row }) =>
      h(UCheckbox, {
        modelValue: row.getIsSelected(),
        'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
        'aria-label': 'Select row'
      })
  },
  {
    accessorKey: 'id',
    header: '#',
    cell: ({ row }) => `#${row.getValue('id')}`
  },
  {
    accessorKey: 'date',
    header: 'Date',
    cell: ({ row }) => {
      return new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      })
    }
  },
  {
    accessorKey: 'status',
    header: 'Status',
    cell: ({ row }) => {
      const color = {
        paid: 'success' as const,
        failed: 'error' as const,
        refunded: 'neutral' as const
      }[row.getValue('status') as string]

      return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
        row.getValue('status')
      )
    }
  },
  {
    accessorKey: 'email',
    header: 'Email'
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(amount)
    }
  }
]

const items = ref<ContextMenuItem[]>([])

function getRowItems(row: TableRow<Payment>) {
  return [
    {
      type: 'label' as const,
      label: 'Actions'
    },
    {
      label: 'Copy payment ID',
      onSelect() {
        copy(row.original.id)

        toast.add({
          title: 'Payment ID copied to clipboard!',
          color: 'success',
          icon: 'i-lucide-circle-check'
        })
      }
    },
    {
      label: row.getIsExpanded() ? 'Collapse' : 'Expand',
      onSelect() {
        row.toggleExpanded()
      }
    },
    {
      type: 'separator' as const
    },
    {
      label: 'View customer'
    },
    {
      label: 'View payment details'
    }
  ]
}

function onContextmenu(_e: Event, row: TableRow<Payment>) {
  items.value = getRowItems(row)
}
</script>

<template>
  <UContextMenu :items="items">
    <UTable :data="data" :columns="columns" class="flex-1" @contextmenu="onContextmenu">
      <template #expanded="{ row }">
        <pre>{{ row.original }}</pre>
      </template>
    </UTable>
  </UContextMenu>
</template>
<script setup lang="ts">
import { h, resolveComponent, ref } from 'vue'
import type { ContextMenuItem, TableColumn, TableRow } from '@nuxt/ui'
import { useClipboard } from '@vueuse/core'

const UBadge = resolveComponent('UBadge')
const UCheckbox = resolveComponent('UCheckbox')

const toast = useToast()
const { copy } = useClipboard()

type Payment = {
  id: string
  date: string
  status: 'paid' | 'failed' | 'refunded'
  email: string
  amount: number
}

const data = ref<Payment[]>([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    status: 'paid',
    email: 'james.anderson@example.com',
    amount: 594
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    status: 'failed',
    email: 'mia.white@example.com',
    amount: 276
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    status: 'refunded',
    email: 'william.brown@example.com',
    amount: 315
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    status: 'paid',
    email: 'emma.davis@example.com',
    amount: 529
  },
  {
    id: '4596',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639
  }
])

const columns: TableColumn<Payment>[] = [
  {
    id: 'select',
    header: ({ table }) =>
      h(UCheckbox, {
        modelValue: table.getIsSomePageRowsSelected()
          ? 'indeterminate'
          : table.getIsAllPageRowsSelected(),
        'onUpdate:modelValue': (value: boolean | 'indeterminate') =>
          table.toggleAllPageRowsSelected(!!value),
        'aria-label': 'Select all'
      }),
    cell: ({ row }) =>
      h(UCheckbox, {
        modelValue: row.getIsSelected(),
        'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
        'aria-label': 'Select row'
      })
  },
  {
    accessorKey: 'id',
    header: '#',
    cell: ({ row }) => `#${row.getValue('id')}`
  },
  {
    accessorKey: 'date',
    header: 'Date',
    cell: ({ row }) => {
      return new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      })
    }
  },
  {
    accessorKey: 'status',
    header: 'Status',
    cell: ({ row }) => {
      const color = {
        paid: 'success' as const,
        failed: 'error' as const,
        refunded: 'neutral' as const
      }[row.getValue('status') as string]

      return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
        row.getValue('status')
      )
    }
  },
  {
    accessorKey: 'email',
    header: 'Email'
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(amount)
    }
  }
]

const items = ref<ContextMenuItem[]>([])

function getRowItems(row: TableRow<Payment>) {
  return [
    {
      type: 'label' as const,
      label: 'Actions'
    },
    {
      label: 'Copy payment ID',
      onSelect() {
        copy(row.original.id)

        toast.add({
          title: 'Payment ID copied to clipboard!',
          color: 'success',
          icon: 'i-lucide-circle-check'
        })
      }
    },
    {
      label: row.getIsExpanded() ? 'Collapse' : 'Expand',
      onSelect() {
        row.toggleExpanded()
      }
    },
    {
      type: 'separator' as const
    },
    {
      label: 'View customer'
    },
    {
      label: 'View payment details'
    }
  ]
}

function onContextmenu(_e: Event, row: TableRow<Payment>) {
  items.value = getRowItems(row)
}
</script>

<template>
  <UContextMenu :items="items">
    <UTable :data="data" :columns="columns" class="flex-1" @contextmenu="onContextmenu">
      <template #expanded="{ row }">
        <pre>{{ row.original }}</pre>
      </template>
    </UTable>
  </UContextMenu>
</template>

With row hover event (Row Hover-Ereignis)

Sie können einen @hover-Listener hinzufügen, um Zeilen schwebefähig zu machen, und eine Popover-oder eine Tooltip-Komponente verwenden, um beispielsweise Zeilendetails anzuzeigen.

Die handler-Funktion empfängt die Event-und TableRow-Instanz als erstes bzw. zweites Argument.
#DateStatusEmailAmount
#4600Mar 11, 15:30paidjames.anderson@example.com€594.00
#4599Mar 11, 10:10failedmia.white@example.com€276.00
#4598Mar 11, 08:50refundedwilliam.brown@example.com€315.00
#4597Mar 10, 19:45paidemma.davis@example.com€529.00
#4596Mar 10, 15:55paidethan.harris@example.com€639.00
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn, TableRow } from '@nuxt/ui'

const UBadge = resolveComponent('UBadge')
const UCheckbox = resolveComponent('UCheckbox')

type Payment = {
  id: string
  date: string
  status: 'paid' | 'failed' | 'refunded'
  email: string
  amount: number
}

const data = ref<Payment[]>([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    status: 'paid',
    email: 'james.anderson@example.com',
    amount: 594
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    status: 'failed',
    email: 'mia.white@example.com',
    amount: 276
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    status: 'refunded',
    email: 'william.brown@example.com',
    amount: 315
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    status: 'paid',
    email: 'emma.davis@example.com',
    amount: 529
  },
  {
    id: '4596',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639
  }
])

const columns: TableColumn<Payment>[] = [
  {
    id: 'select',
    header: ({ table }) =>
      h(UCheckbox, {
        modelValue: table.getIsSomePageRowsSelected()
          ? 'indeterminate'
          : table.getIsAllPageRowsSelected(),
        'onUpdate:modelValue': (value: boolean | 'indeterminate') =>
          table.toggleAllPageRowsSelected(!!value),
        'aria-label': 'Select all'
      }),
    cell: ({ row }) =>
      h(UCheckbox, {
        modelValue: row.getIsSelected(),
        'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
        'aria-label': 'Select row'
      })
  },
  {
    accessorKey: 'id',
    header: '#',
    cell: ({ row }) => `#${row.getValue('id')}`
  },
  {
    accessorKey: 'date',
    header: 'Date',
    cell: ({ row }) => {
      return new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      })
    }
  },
  {
    accessorKey: 'status',
    header: 'Status',
    cell: ({ row }) => {
      const color = {
        paid: 'success' as const,
        failed: 'error' as const,
        refunded: 'neutral' as const
      }[row.getValue('status') as string]

      return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
        row.getValue('status')
      )
    }
  },
  {
    accessorKey: 'email',
    header: 'Email'
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(amount)
    }
  }
]

const anchor = ref({ x: 0, y: 0 })

const reference = computed(() => ({
  getBoundingClientRect: () =>
    ({
      width: 0,
      height: 0,
      left: anchor.value.x,
      right: anchor.value.x,
      top: anchor.value.y,
      bottom: anchor.value.y,
      ...anchor.value
    }) as DOMRect
}))

const open = ref(false)
const openDebounced = refDebounced(open, 10)
const selectedRow = ref<TableRow<Payment> | null>(null)

function onHover(_e: Event, row: TableRow<Payment> | null) {
  selectedRow.value = row

  open.value = !!row
}
</script>

<template>
  <div class="flex w-full flex-1 gap-1">
    <UTable
      :data="data"
      :columns="columns"
      class="flex-1"
      @pointermove="
        (ev: PointerEvent) => {
          anchor.x = ev.clientX
          anchor.y = ev.clientY
        }
      "
      @hover="onHover"
    />

    <UPopover
      :content="{ side: 'top', sideOffset: 16, updatePositionStrategy: 'always' }"
      :open="openDebounced"
      :reference="reference"
    >
      <template #content>
        <div class="p-4">
          {{ selectedRow?.original?.id }}
        </div>
      </template>
    </UPopover>
  </div>
</template>
<script setup lang="ts">
import { refDebounced } from '@vueuse/core'
import { h, resolveComponent, ref, computed } from 'vue'
import type { TableColumn, TableRow } from '@nuxt/ui'

const UBadge = resolveComponent('UBadge')
const UCheckbox = resolveComponent('UCheckbox')

type Payment = {
  id: string
  date: string
  status: 'paid' | 'failed' | 'refunded'
  email: string
  amount: number
}

const data = ref<Payment[]>([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    status: 'paid',
    email: 'james.anderson@example.com',
    amount: 594
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    status: 'failed',
    email: 'mia.white@example.com',
    amount: 276
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    status: 'refunded',
    email: 'william.brown@example.com',
    amount: 315
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    status: 'paid',
    email: 'emma.davis@example.com',
    amount: 529
  },
  {
    id: '4596',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639
  }
])

const columns: TableColumn<Payment>[] = [
  {
    id: 'select',
    header: ({ table }) =>
      h(UCheckbox, {
        modelValue: table.getIsSomePageRowsSelected()
          ? 'indeterminate'
          : table.getIsAllPageRowsSelected(),
        'onUpdate:modelValue': (value: boolean | 'indeterminate') =>
          table.toggleAllPageRowsSelected(!!value),
        'aria-label': 'Select all'
      }),
    cell: ({ row }) =>
      h(UCheckbox, {
        modelValue: row.getIsSelected(),
        'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
        'aria-label': 'Select row'
      })
  },
  {
    accessorKey: 'id',
    header: '#',
    cell: ({ row }) => `#${row.getValue('id')}`
  },
  {
    accessorKey: 'date',
    header: 'Date',
    cell: ({ row }) => {
      return new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      })
    }
  },
  {
    accessorKey: 'status',
    header: 'Status',
    cell: ({ row }) => {
      const color = {
        paid: 'success' as const,
        failed: 'error' as const,
        refunded: 'neutral' as const
      }[row.getValue('status') as string]

      return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
        row.getValue('status')
      )
    }
  },
  {
    accessorKey: 'email',
    header: 'Email'
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(amount)
    }
  }
]

const anchor = ref({ x: 0, y: 0 })

const reference = computed(() => ({
  getBoundingClientRect: () =>
    ({
      width: 0,
      height: 0,
      left: anchor.value.x,
      right: anchor.value.x,
      top: anchor.value.y,
      bottom: anchor.value.y,
      ...anchor.value
    }) as DOMRect
}))

const open = ref(false)
const openDebounced = refDebounced(open, 10)
const selectedRow = ref<TableRow<Payment> | null>(null)

function onHover(_e: Event, row: TableRow<Payment> | null) {
  selectedRow.value = row

  open.value = !!row
}
</script>

<template>
  <div class="flex w-full flex-1 gap-1">
    <UTable
      :data="data"
      :columns="columns"
      class="flex-1"
      @pointermove="
        (ev: PointerEvent) => {
          anchor.x = ev.clientX
          anchor.y = ev.clientY
        }
      "
      @hover="onHover"
    />

    <UPopover
      :content="{ side: 'top', sideOffset: 16, updatePositionStrategy: 'always' }"
      :open="openDebounced"
      :reference="reference"
    >
      <template #content>
        <div class="p-4">
          {{ selectedRow?.original?.id }}
        </div>
      </template>
    </UPopover>
  </div>
</template>
Dieses Beispiel ähnelt dem Popover with following cursor example und verwendet ein xph4440x, um zu verhindern, dass sich das Popover zu schnell öffnet und schließt, wenn der Cursor von einer Zeile in eine andere bewegt wird.

With Spalte Fußzeile

Sie können der Spaltendefinition eine footer-Eigenschaft hinzufügen, um eine Fußzeile für die Spalte zu rendern.

#DateStatusEmailAmount
#4600Mar 11, 15:30paidjames.anderson@example.com€594.00
#4599Mar 11, 10:10failedmia.white@example.com€276.00
#4598Mar 11, 08:50refundedwilliam.brown@example.com€315.00
#4597Mar 10, 19:45paidemma.davis@example.com€529.00
#4596Mar 10, 15:55paidethan.harris@example.com€639.00
Total: €2,353.00
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn, TableRow } from '@nuxt/ui'

const UBadge = resolveComponent('UBadge')

type Payment = {
  id: string
  date: string
  status: 'paid' | 'failed' | 'refunded'
  email: string
  amount: number
}

const data = ref<Payment[]>([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    status: 'paid',
    email: 'james.anderson@example.com',
    amount: 594
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    status: 'failed',
    email: 'mia.white@example.com',
    amount: 276
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    status: 'refunded',
    email: 'william.brown@example.com',
    amount: 315
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    status: 'paid',
    email: 'emma.davis@example.com',
    amount: 529
  },
  {
    id: '4596',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639
  }
])

const columns: TableColumn<Payment>[] = [
  {
    accessorKey: 'id',
    header: '#',
    cell: ({ row }) => `#${row.getValue('id')}`
  },
  {
    accessorKey: 'date',
    header: 'Date',
    cell: ({ row }) => {
      return new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      })
    }
  },
  {
    accessorKey: 'status',
    header: 'Status',
    cell: ({ row }) => {
      const color = {
        paid: 'success' as const,
        failed: 'error' as const,
        refunded: 'neutral' as const
      }[row.getValue('status') as string]

      return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
        row.getValue('status')
      )
    }
  },
  {
    accessorKey: 'email',
    header: 'Email'
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    footer: ({ column }) => {
      const total = column
        .getFacetedRowModel()
        .rows.reduce(
          (acc: number, row: TableRow<Payment>) => acc + Number.parseFloat(row.getValue('amount')),
          0
        )
      const formatted = new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(total)
      return `Total: ${formatted}`
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(amount)
    }
  }
]
</script>

<template>
  <UTable :data="data" :columns="columns" class="flex-1" />
</template>
<script setup lang="ts">
import { h, resolveComponent, ref } from 'vue'
import type { TableColumn, TableRow } from '@nuxt/ui'

const UBadge = resolveComponent('UBadge')

type Payment = {
  id: string
  date: string
  status: 'paid' | 'failed' | 'refunded'
  email: string
  amount: number
}

const data = ref<Payment[]>([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    status: 'paid',
    email: 'james.anderson@example.com',
    amount: 594
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    status: 'failed',
    email: 'mia.white@example.com',
    amount: 276
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    status: 'refunded',
    email: 'william.brown@example.com',
    amount: 315
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    status: 'paid',
    email: 'emma.davis@example.com',
    amount: 529
  },
  {
    id: '4596',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639
  }
])

const columns: TableColumn<Payment>[] = [
  {
    accessorKey: 'id',
    header: '#',
    cell: ({ row }) => `#${row.getValue('id')}`
  },
  {
    accessorKey: 'date',
    header: 'Date',
    cell: ({ row }) => {
      return new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      })
    }
  },
  {
    accessorKey: 'status',
    header: 'Status',
    cell: ({ row }) => {
      const color = {
        paid: 'success' as const,
        failed: 'error' as const,
        refunded: 'neutral' as const
      }[row.getValue('status') as string]

      return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
        row.getValue('status')
      )
    }
  },
  {
    accessorKey: 'email',
    header: 'Email'
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    footer: ({ column }) => {
      const total = column
        .getFacetedRowModel()
        .rows.reduce(
          (acc: number, row: TableRow<Payment>) => acc + Number.parseFloat(row.getValue('amount')),
          0
        )
      const formatted = new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(total)
      return `Total: ${formatted}`
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(amount)
    }
  }
]
</script>

<template>
  <UTable :data="data" :columns="columns" class="flex-1" />
</template>

With Spaltenspanne

Sie können die Eigenschaften colspan und rowspan in der Spalte meta zum Zusammenführen von Zellen verwenden. Diese Eigenschaften akzeptieren einen statischen Wert oder eine Funktion, die die Zelle empfängt und den Wert span zurückgibt.

Wenn Sie rowspan verwenden, müssen Zellen, die von der Spanne einer vorherigen Zeile "absorbiert" werden, visuell ausgeblendet werden. Verwenden Sie die class-Meta mit einer Funktion, die 'hidden' für diese Zellen zurückgibt.
CategoryNamePriceStock
ElectronicsLaptop$999.0045
Phone$699.00120
Tablet$499.0078
ClothingT-Shirt$29.00200
Jeans$59.00150
<script setup lang="ts">
import type { TableColumn } from '@nuxt/ui'
import type { Cell } from '@tanstack/vue-table'

type Product = {
  category: string
  name: string
  price: number
  stock: number
}

const data = ref<Product[]>([
  { category: 'Electronics', name: 'Laptop', price: 999, stock: 45 },
  { category: 'Electronics', name: 'Phone', price: 699, stock: 120 },
  { category: 'Electronics', name: 'Tablet', price: 499, stock: 78 },
  { category: 'Clothing', name: 'T-Shirt', price: 29, stock: 200 },
  { category: 'Clothing', name: 'Jeans', price: 59, stock: 150 }
])

function getCategoryRowSpan(cell: Cell<Product, unknown>) {
  const category = cell.row.original.category
  const rows = cell.getContext().table.getRowModel().rows
  const rowIndex = rows.findIndex((r: (typeof rows)[number]) => r.id === cell.row.id)

  if (rowIndex > 0 && rows[rowIndex - 1]!.original.category === category) {
    return '1'
  }

  let span = 1
  for (let i = rowIndex + 1; i < rows.length; i++) {
    if (rows[i]!.original.category === category) span++
    else break
  }

  return `${span}`
}

function getCategoryClass(cell: Cell<Product, unknown>) {
  const category = cell.row.original.category
  const rows = cell.getContext().table.getRowModel().rows
  const rowIndex = rows.findIndex((r: (typeof rows)[number]) => r.id === cell.row.id)

  if (rowIndex > 0 && rows[rowIndex - 1]!.original.category === category) {
    return 'hidden'
  }

  return 'font-medium align-middle border-r border-default'
}

const columns: TableColumn<Product>[] = [
  {
    accessorKey: 'category',
    header: 'Category',
    meta: {
      rowspan: {
        td: getCategoryRowSpan
      },
      class: {
        td: getCategoryClass
      }
    }
  },
  {
    accessorKey: 'name',
    header: 'Name'
  },
  {
    accessorKey: 'price',
    header: 'Price',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right'
      }
    },
    cell: ({ row }) => {
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'USD'
      }).format(row.getValue('price'))
    }
  },
  {
    accessorKey: 'stock',
    header: 'Stock',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right'
      }
    }
  }
]
</script>

<template>
  <UTable :data="data" :columns="columns" class="flex-1" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { TableColumn } from '@nuxt/ui'
import type { Cell } from '@tanstack/vue-table'

type Product = {
  category: string
  name: string
  price: number
  stock: number
}

const data = ref<Product[]>([
  { category: 'Electronics', name: 'Laptop', price: 999, stock: 45 },
  { category: 'Electronics', name: 'Phone', price: 699, stock: 120 },
  { category: 'Electronics', name: 'Tablet', price: 499, stock: 78 },
  { category: 'Clothing', name: 'T-Shirt', price: 29, stock: 200 },
  { category: 'Clothing', name: 'Jeans', price: 59, stock: 150 }
])

function getCategoryRowSpan(cell: Cell<Product, unknown>) {
  const category = cell.row.original.category
  const rows = cell.getContext().table.getRowModel().rows
  const rowIndex = rows.findIndex((r: (typeof rows)[number]) => r.id === cell.row.id)

  if (rowIndex > 0 && rows[rowIndex - 1]!.original.category === category) {
    return '1'
  }

  let span = 1
  for (let i = rowIndex + 1; i < rows.length; i++) {
    if (rows[i]!.original.category === category) span++
    else break
  }

  return `${span}`
}

function getCategoryClass(cell: Cell<Product, unknown>) {
  const category = cell.row.original.category
  const rows = cell.getContext().table.getRowModel().rows
  const rowIndex = rows.findIndex((r: (typeof rows)[number]) => r.id === cell.row.id)

  if (rowIndex > 0 && rows[rowIndex - 1]!.original.category === category) {
    return 'hidden'
  }

  return 'font-medium align-middle border-r border-default'
}

const columns: TableColumn<Product>[] = [
  {
    accessorKey: 'category',
    header: 'Category',
    meta: {
      rowspan: {
        td: getCategoryRowSpan
      },
      class: {
        td: getCategoryClass
      }
    }
  },
  {
    accessorKey: 'name',
    header: 'Name'
  },
  {
    accessorKey: 'price',
    header: 'Price',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right'
      }
    },
    cell: ({ row }) => {
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'USD'
      }).format(row.getValue('price'))
    }
  },
  {
    accessorKey: 'stock',
    header: 'Stock',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right'
      }
    }
  }
]
</script>

<template>
  <UTable :data="data" :columns="columns" class="flex-1" />
</template>

With Spalte Sortierung

Sie können eine Spalte header aktualisieren, um eine Button-Komponente innerhalb der header zu rendern, um den Sortierstatus mithilfe der TanStack-Tabelle Sorting APIs umzuschalten.

Dies setzt aria-sort auf die <th>, so dass Screenreader den aktuellen Sortierstatus der Spalte lesen können: none, ascending oder descending.

#DateStatusAmount
#4597Mar 10, 19:45paidemma.davis@example.com€529.00
#4596Mar 10, 15:55paidethan.harris@example.com€639.00
#4600Mar 11, 15:30paidjames.anderson@example.com€594.00
#4599Mar 11, 10:10failedmia.white@example.com€276.00
#4598Mar 11, 08:50refundedwilliam.brown@example.com€315.00
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'

const UBadge = resolveComponent('UBadge')
const UButton = resolveComponent('UButton')

type Payment = {
  id: string
  date: string
  status: 'paid' | 'failed' | 'refunded'
  email: string
  amount: number
}

const data = ref<Payment[]>([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    status: 'paid',
    email: 'james.anderson@example.com',
    amount: 594
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    status: 'failed',
    email: 'mia.white@example.com',
    amount: 276
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    status: 'refunded',
    email: 'william.brown@example.com',
    amount: 315
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    status: 'paid',
    email: 'emma.davis@example.com',
    amount: 529
  },
  {
    id: '4596',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639
  }
])

const columns: TableColumn<Payment>[] = [
  {
    accessorKey: 'id',
    header: '#',
    cell: ({ row }) => `#${row.getValue('id')}`
  },
  {
    accessorKey: 'date',
    header: 'Date',
    cell: ({ row }) => {
      return new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      })
    }
  },
  {
    accessorKey: 'status',
    header: 'Status',
    cell: ({ row }) => {
      const color = {
        paid: 'success' as const,
        failed: 'error' as const,
        refunded: 'neutral' as const
      }[row.getValue('status') as string]

      return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
        row.getValue('status')
      )
    }
  },
  {
    accessorKey: 'email',
    enableSorting: true,
    header: ({ column }) => {
      const isSorted = column.getIsSorted()

      return h(UButton, {
        color: 'neutral',
        variant: 'ghost',
        label: 'Email',
        icon: isSorted
          ? isSorted === 'asc'
            ? 'i-lucide-arrow-up-narrow-wide'
            : 'i-lucide-arrow-down-wide-narrow'
          : 'i-lucide-arrow-up-down',
        class: '-mx-2.5',
        onClick: () => column.toggleSorting(column.getIsSorted() === 'asc')
      })
    }
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(amount)
    }
  }
]

const sorting = ref([
  {
    id: 'email',
    desc: false
  }
])
</script>

<template>
  <UTable v-model:sorting="sorting" :data="data" :columns="columns" class="flex-1" />
</template>
<script setup lang="ts">
import { h, resolveComponent, ref } from 'vue'
import type { TableColumn } from '@nuxt/ui'

const UBadge = resolveComponent('UBadge')
const UButton = resolveComponent('UButton')

type Payment = {
  id: string
  date: string
  status: 'paid' | 'failed' | 'refunded'
  email: string
  amount: number
}

const data = ref<Payment[]>([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    status: 'paid',
    email: 'james.anderson@example.com',
    amount: 594
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    status: 'failed',
    email: 'mia.white@example.com',
    amount: 276
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    status: 'refunded',
    email: 'william.brown@example.com',
    amount: 315
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    status: 'paid',
    email: 'emma.davis@example.com',
    amount: 529
  },
  {
    id: '4596',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639
  }
])

const columns: TableColumn<Payment>[] = [
  {
    accessorKey: 'id',
    header: '#',
    cell: ({ row }) => `#${row.getValue('id')}`
  },
  {
    accessorKey: 'date',
    header: 'Date',
    cell: ({ row }) => {
      return new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      })
    }
  },
  {
    accessorKey: 'status',
    header: 'Status',
    cell: ({ row }) => {
      const color = {
        paid: 'success' as const,
        failed: 'error' as const,
        refunded: 'neutral' as const
      }[row.getValue('status') as string]

      return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
        row.getValue('status')
      )
    }
  },
  {
    accessorKey: 'email',
    enableSorting: true,
    header: ({ column }) => {
      const isSorted = column.getIsSorted()

      return h(UButton, {
        color: 'neutral',
        variant: 'ghost',
        label: 'Email',
        icon: isSorted
          ? isSorted === 'asc'
            ? 'i-lucide-arrow-up-narrow-wide'
            : 'i-lucide-arrow-down-wide-narrow'
          : 'i-lucide-arrow-up-down',
        class: '-mx-2.5',
        onClick: () => column.toggleSorting(column.getIsSorted() === 'asc')
      })
    }
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(amount)
    }
  }
]

const sorting = ref([
  {
    id: 'email',
    desc: false
  }
])
</script>

<template>
  <UTable v-model:sorting="sorting" :data="data" :columns="columns" class="flex-1" />
</template>
Sie können die sorting-prop verwenden, um den Sortierzustand der Spalten zu steuern (kann mit v-model gebunden werden).

Sie können auch eine wiederverwendbare Komponente erstellen, um jede Spaltenüberschrift sortierbar zu machen.

#4596Mar 10, 15:55paidethan.harris@example.com€639.00
#4597Mar 10, 19:45paidemma.davis@example.com€529.00
#4598Mar 11, 08:50refundedwilliam.brown@example.com€315.00
#4599Mar 11, 10:10failedmia.white@example.com€276.00
#4600Mar 11, 15:30paidjames.anderson@example.com€594.00
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'
import type { Column } from '@tanstack/vue-table'

const UBadge = resolveComponent('UBadge')
const UButton = resolveComponent('UButton')
const UDropdownMenu = resolveComponent('UDropdownMenu')

type Payment = {
  id: string
  date: string
  status: 'paid' | 'failed' | 'refunded'
  email: string
  amount: number
}

const data = ref<Payment[]>([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    status: 'paid',
    email: 'james.anderson@example.com',
    amount: 594
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    status: 'failed',
    email: 'mia.white@example.com',
    amount: 276
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    status: 'refunded',
    email: 'william.brown@example.com',
    amount: 315
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    status: 'paid',
    email: 'emma.davis@example.com',
    amount: 529
  },
  {
    id: '4596',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639
  }
])

const columns: TableColumn<Payment>[] = [
  {
    accessorKey: 'id',
    enableSorting: true,
    header: ({ column }) => getHeader(column, 'ID'),
    cell: ({ row }) => `#${row.getValue('id')}`
  },
  {
    accessorKey: 'date',
    enableSorting: true,
    header: ({ column }) => getHeader(column, 'Date'),
    cell: ({ row }) => {
      return new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      })
    }
  },
  {
    accessorKey: 'status',
    enableSorting: true,
    header: ({ column }) => getHeader(column, 'Status'),
    cell: ({ row }) => {
      const color = {
        paid: 'success' as const,
        failed: 'error' as const,
        refunded: 'neutral' as const
      }[row.getValue('status') as string]

      return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
        row.getValue('status')
      )
    }
  },
  {
    accessorKey: 'email',
    enableSorting: true,
    header: ({ column }) => getHeader(column, 'Email')
  },
  {
    accessorKey: 'amount',
    enableSorting: true,
    header: ({ column }) => getHeader(column, 'Amount'),
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(amount)
    }
  }
]

function getHeader(column: Column<Payment>, label: string) {
  const isSorted = column.getIsSorted()

  return h(
    UDropdownMenu,
    {
      content: {
        align: 'start'
      },
      'aria-label': 'Actions dropdown',
      items: [
        {
          label: 'Asc',
          type: 'checkbox',
          icon: 'i-lucide-arrow-up-narrow-wide',
          checked: isSorted === 'asc',
          onSelect: () => {
            if (isSorted === 'asc') {
              column.clearSorting()
            } else {
              column.toggleSorting(false)
            }
          }
        },
        {
          label: 'Desc',
          icon: 'i-lucide-arrow-down-wide-narrow',
          type: 'checkbox',
          checked: isSorted === 'desc',
          onSelect: () => {
            if (isSorted === 'desc') {
              column.clearSorting()
            } else {
              column.toggleSorting(true)
            }
          }
        }
      ]
    },
    () =>
      h(UButton, {
        color: 'neutral',
        variant: 'ghost',
        label,
        icon: isSorted
          ? isSorted === 'asc'
            ? 'i-lucide-arrow-up-narrow-wide'
            : 'i-lucide-arrow-down-wide-narrow'
          : 'i-lucide-arrow-up-down',
        class: '-mx-2.5 data-[state=open]:bg-elevated',
        'aria-label': `Sort by ${isSorted === 'asc' ? 'descending' : 'ascending'}`
      })
  )
}

const sorting = ref([
  {
    id: 'id',
    desc: false
  }
])
</script>

<template>
  <UTable v-model:sorting="sorting" :data="data" :columns="columns" class="flex-1" />
</template>
<script setup lang="ts">
import { h, resolveComponent, ref } from 'vue'
import type { TableColumn } from '@nuxt/ui'
import type { Column } from '@tanstack/vue-table'

const UBadge = resolveComponent('UBadge')
const UButton = resolveComponent('UButton')
const UDropdownMenu = resolveComponent('UDropdownMenu')

type Payment = {
  id: string
  date: string
  status: 'paid' | 'failed' | 'refunded'
  email: string
  amount: number
}

const data = ref<Payment[]>([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    status: 'paid',
    email: 'james.anderson@example.com',
    amount: 594
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    status: 'failed',
    email: 'mia.white@example.com',
    amount: 276
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    status: 'refunded',
    email: 'william.brown@example.com',
    amount: 315
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    status: 'paid',
    email: 'emma.davis@example.com',
    amount: 529
  },
  {
    id: '4596',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639
  }
])

const columns: TableColumn<Payment>[] = [
  {
    accessorKey: 'id',
    enableSorting: true,
    header: ({ column }) => getHeader(column, 'ID'),
    cell: ({ row }) => `#${row.getValue('id')}`
  },
  {
    accessorKey: 'date',
    enableSorting: true,
    header: ({ column }) => getHeader(column, 'Date'),
    cell: ({ row }) => {
      return new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      })
    }
  },
  {
    accessorKey: 'status',
    enableSorting: true,
    header: ({ column }) => getHeader(column, 'Status'),
    cell: ({ row }) => {
      const color = {
        paid: 'success' as const,
        failed: 'error' as const,
        refunded: 'neutral' as const
      }[row.getValue('status') as string]

      return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
        row.getValue('status')
      )
    }
  },
  {
    accessorKey: 'email',
    enableSorting: true,
    header: ({ column }) => getHeader(column, 'Email')
  },
  {
    accessorKey: 'amount',
    enableSorting: true,
    header: ({ column }) => getHeader(column, 'Amount'),
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(amount)
    }
  }
]

function getHeader(column: Column<Payment>, label: string) {
  const isSorted = column.getIsSorted()

  return h(
    UDropdownMenu,
    {
      content: {
        align: 'start'
      },
      'aria-label': 'Actions dropdown',
      items: [
        {
          label: 'Asc',
          type: 'checkbox',
          icon: 'i-lucide-arrow-up-narrow-wide',
          checked: isSorted === 'asc',
          onSelect: () => {
            if (isSorted === 'asc') {
              column.clearSorting()
            } else {
              column.toggleSorting(false)
            }
          }
        },
        {
          label: 'Desc',
          icon: 'i-lucide-arrow-down-wide-narrow',
          type: 'checkbox',
          checked: isSorted === 'desc',
          onSelect: () => {
            if (isSorted === 'desc') {
              column.clearSorting()
            } else {
              column.toggleSorting(true)
            }
          }
        }
      ]
    },
    () =>
      h(UButton, {
        color: 'neutral',
        variant: 'ghost',
        label,
        icon: isSorted
          ? isSorted === 'asc'
            ? 'i-lucide-arrow-up-narrow-wide'
            : 'i-lucide-arrow-down-wide-narrow'
          : 'i-lucide-arrow-up-down',
        class: '-mx-2.5 data-[state=open]:bg-elevated',
        'aria-label': `Sort by ${isSorted === 'asc' ? 'descending' : 'ascending'}`
      })
  )
}

const sorting = ref([
  {
    id: 'id',
    desc: false
  }
])
</script>

<template>
  <UTable v-model:sorting="sorting" :data="data" :columns="columns" class="flex-1" />
</template>
In diesem Beispiel verwenden wir eine Funktion, um die Spaltenüberschrift zu definieren, aber Sie können auch eine tatsächliche Komponente erstellen.

Mit Column Pinning

Sie können eine Spalte header aktualisieren, um eine Button-Komponente innerhalb der header zu rendern, um den Pinning-Status mithilfe der TanStack-Tabelle Column Pinning APIs umzuschalten.

Wenn Sie Spalten-Pinning verwenden, sollten Sie explizite size-Werte für Ihre Spalten definieren, um eine ordnungsgemäße Handhabung der Spaltenbreite sicherzustellen, insbesondere bei mehreren angehefteten Spalten.
#46000000000000000000000000000000000000002024-03-11T15:30:00paidjames.anderson@example.com€594,000.00
#45990000000000000000000000000000000000002024-03-11T10:10:00failedmia.white@example.com€276,000.00
#45980000000000000000000000000000000000002024-03-11T08:50:00refundedwilliam.brown@example.com€315,000.00
#45970000000000000000000000000000000000002024-03-10T19:45:00paidemma.davis@example.com€5,290,000.00
#45960000000000000000000000000000000000002024-03-10T15:55:00paidethan.harris@example.com€639,000.00
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'
import type { Column } from '@tanstack/vue-table'

const UBadge = resolveComponent('UBadge')
const UButton = resolveComponent('UButton')

type Payment = {
  id: string
  date: string
  status: 'paid' | 'failed' | 'refunded'
  email: string
  amount: number
}

const data = ref<Payment[]>([
  {
    id: '4600000000000000000000000000000000000000',
    date: '2024-03-11T15:30:00',
    status: 'paid',
    email: 'james.anderson@example.com',
    amount: 594000
  },
  {
    id: '4599000000000000000000000000000000000000',
    date: '2024-03-11T10:10:00',
    status: 'failed',
    email: 'mia.white@example.com',
    amount: 276000
  },
  {
    id: '4598000000000000000000000000000000000000',
    date: '2024-03-11T08:50:00',
    status: 'refunded',
    email: 'william.brown@example.com',
    amount: 315000
  },
  {
    id: '4597000000000000000000000000000000000000',
    date: '2024-03-10T19:45:00',
    status: 'paid',
    email: 'emma.davis@example.com',
    amount: 5290000
  },
  {
    id: '4596000000000000000000000000000000000000',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639000
  }
])

const columns: TableColumn<Payment>[] = [
  {
    accessorKey: 'id',
    header: ({ column }) => getHeader(column, 'ID', 'left'),
    cell: ({ row }) => `#${row.getValue('id')}`,
    size: 381
  },
  {
    accessorKey: 'date',
    header: ({ column }) => getHeader(column, 'Date', 'left'),
    size: 172
  },
  {
    accessorKey: 'status',
    header: ({ column }) => getHeader(column, 'Status', 'left'),
    cell: ({ row }) => {
      const color = {
        paid: 'success' as const,
        failed: 'error' as const,
        refunded: 'neutral' as const
      }[row.getValue('status') as string]

      return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
        row.getValue('status')
      )
    },
    size: 103
  },
  {
    accessorKey: 'email',
    header: ({ column }) => getHeader(column, 'Email', 'left'),
    size: 232
  },
  {
    accessorKey: 'amount',
    header: ({ column }) => getHeader(column, 'Amount', 'right'),
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(amount)
    },
    size: 130
  }
]

function getHeader(column: Column<Payment>, label: string, position: 'left' | 'right') {
  const isPinned = column.getIsPinned()

  return h(UButton, {
    color: 'neutral',
    variant: 'ghost',
    label,
    icon: isPinned ? 'i-lucide-pin-off' : 'i-lucide-pin',
    class: '-mx-2.5',
    onClick() {
      column.pin(isPinned === position ? false : position)
    }
  })
}

const columnPinning = ref({
  left: ['id'],
  right: ['amount']
})
</script>

<template>
  <UTable v-model:column-pinning="columnPinning" :data="data" :columns="columns" class="flex-1" />
</template>
<script setup lang="ts">
import { h, resolveComponent, ref } from 'vue'
import type { TableColumn } from '@nuxt/ui'
import type { Column } from '@tanstack/vue-table'

const UBadge = resolveComponent('UBadge')
const UButton = resolveComponent('UButton')

type Payment = {
  id: string
  date: string
  status: 'paid' | 'failed' | 'refunded'
  email: string
  amount: number
}

const data = ref<Payment[]>([
  {
    id: '4600000000000000000000000000000000000000',
    date: '2024-03-11T15:30:00',
    status: 'paid',
    email: 'james.anderson@example.com',
    amount: 594000
  },
  {
    id: '4599000000000000000000000000000000000000',
    date: '2024-03-11T10:10:00',
    status: 'failed',
    email: 'mia.white@example.com',
    amount: 276000
  },
  {
    id: '4598000000000000000000000000000000000000',
    date: '2024-03-11T08:50:00',
    status: 'refunded',
    email: 'william.brown@example.com',
    amount: 315000
  },
  {
    id: '4597000000000000000000000000000000000000',
    date: '2024-03-10T19:45:00',
    status: 'paid',
    email: 'emma.davis@example.com',
    amount: 5290000
  },
  {
    id: '4596000000000000000000000000000000000000',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639000
  }
])

const columns: TableColumn<Payment>[] = [
  {
    accessorKey: 'id',
    header: ({ column }) => getHeader(column, 'ID', 'left'),
    cell: ({ row }) => `#${row.getValue('id')}`,
    size: 381
  },
  {
    accessorKey: 'date',
    header: ({ column }) => getHeader(column, 'Date', 'left'),
    size: 172
  },
  {
    accessorKey: 'status',
    header: ({ column }) => getHeader(column, 'Status', 'left'),
    cell: ({ row }) => {
      const color = {
        paid: 'success' as const,
        failed: 'error' as const,
        refunded: 'neutral' as const
      }[row.getValue('status') as string]

      return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
        row.getValue('status')
      )
    },
    size: 103
  },
  {
    accessorKey: 'email',
    header: ({ column }) => getHeader(column, 'Email', 'left'),
    size: 232
  },
  {
    accessorKey: 'amount',
    header: ({ column }) => getHeader(column, 'Amount', 'right'),
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(amount)
    },
    size: 130
  }
]

function getHeader(column: Column<Payment>, label: string, position: 'left' | 'right') {
  const isPinned = column.getIsPinned()

  return h(UButton, {
    color: 'neutral',
    variant: 'ghost',
    label,
    icon: isPinned ? 'i-lucide-pin-off' : 'i-lucide-pin',
    class: '-mx-2.5',
    onClick() {
      column.pin(isPinned === position ? false : position)
    }
  })
}

const columnPinning = ref({
  left: ['id'],
  right: ['amount']
})
</script>

<template>
  <UTable v-model:column-pinning="columnPinning" :data="data" :columns="columns" class="flex-1" />
</template>
Sie können die column-pinning-prop verwenden, um den Pinning-Zustand der Spalten zu steuern (kann mit v-model gebunden werden).

Mit Spaltensichtbarkeit

Sie können eine DropdownMenu-Komponente verwenden, um die Sichtbarkeit der Spalten mithilfe der TanStack-Tabelle Column Visibility APIs umzuschalten.

DateStatusEmailAmount
Mar 11, 15:30paidjames.anderson@example.com€594.00
Mar 11, 10:10failedmia.white@example.com€276.00
Mar 11, 08:50refundedwilliam.brown@example.com€315.00
Mar 10, 19:45paidemma.davis@example.com€529.00
Mar 10, 15:55paidethan.harris@example.com€639.00
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import { upperFirst } from 'scule'
import type { TableColumn } from '@nuxt/ui'

const UBadge = resolveComponent('UBadge')

type Payment = {
  id: string
  date: string
  status: 'paid' | 'failed' | 'refunded'
  email: string
  amount: number
}

const data = ref<Payment[]>([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    status: 'paid',
    email: 'james.anderson@example.com',
    amount: 594
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    status: 'failed',
    email: 'mia.white@example.com',
    amount: 276
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    status: 'refunded',
    email: 'william.brown@example.com',
    amount: 315
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    status: 'paid',
    email: 'emma.davis@example.com',
    amount: 529
  },
  {
    id: '4596',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639
  }
])

const columns: TableColumn<Payment>[] = [
  {
    accessorKey: 'id',
    header: '#',
    cell: ({ row }) => `#${row.getValue('id')}`
  },
  {
    accessorKey: 'date',
    header: 'Date',
    cell: ({ row }) => {
      return new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      })
    }
  },
  {
    accessorKey: 'status',
    header: 'Status',
    cell: ({ row }) => {
      const color = {
        paid: 'success' as const,
        failed: 'error' as const,
        refunded: 'neutral' as const
      }[row.getValue('status') as string]

      return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
        row.getValue('status')
      )
    }
  },
  {
    accessorKey: 'email',
    header: 'Email'
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(amount)
    }
  }
]

const table = useTemplateRef('table')

const columnVisibility = ref({
  id: false
})
</script>

<template>
  <div class="flex flex-col flex-1 w-full">
    <div class="flex justify-end px-4 py-3.5 border-b border-accented">
      <UDropdownMenu
        :items="
          table?.tableApi
            ?.getAllColumns()
            .filter((column) => column.getCanHide())
            .map((column) => ({
              label: upperFirst(column.id),
              type: 'checkbox' as const,
              checked: column.getIsVisible(),
              onUpdateChecked(checked: boolean) {
                table?.tableApi?.getColumn(column.id)?.toggleVisibility(!!checked)
              },
              onSelect(e: Event) {
                e.preventDefault()
              }
            }))
        "
        :content="{ align: 'end' }"
      >
        <UButton
          label="Columns"
          color="neutral"
          variant="outline"
          trailing-icon="i-lucide-chevron-down"
        />
      </UDropdownMenu>
    </div>

    <UTable
      ref="table"
      v-model:column-visibility="columnVisibility"
      :data="data"
      :columns="columns"
    />
  </div>
</template>
<script setup lang="ts">
import { h, resolveComponent, ref, useTemplateRef } from 'vue'
import { upperFirst } from 'scule'
import type { TableColumn } from '@nuxt/ui'

const UBadge = resolveComponent('UBadge')

type Payment = {
  id: string
  date: string
  status: 'paid' | 'failed' | 'refunded'
  email: string
  amount: number
}

const data = ref<Payment[]>([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    status: 'paid',
    email: 'james.anderson@example.com',
    amount: 594
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    status: 'failed',
    email: 'mia.white@example.com',
    amount: 276
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    status: 'refunded',
    email: 'william.brown@example.com',
    amount: 315
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    status: 'paid',
    email: 'emma.davis@example.com',
    amount: 529
  },
  {
    id: '4596',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639
  }
])

const columns: TableColumn<Payment>[] = [
  {
    accessorKey: 'id',
    header: '#',
    cell: ({ row }) => `#${row.getValue('id')}`
  },
  {
    accessorKey: 'date',
    header: 'Date',
    cell: ({ row }) => {
      return new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      })
    }
  },
  {
    accessorKey: 'status',
    header: 'Status',
    cell: ({ row }) => {
      const color = {
        paid: 'success' as const,
        failed: 'error' as const,
        refunded: 'neutral' as const
      }[row.getValue('status') as string]

      return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
        row.getValue('status')
      )
    }
  },
  {
    accessorKey: 'email',
    header: 'Email'
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(amount)
    }
  }
]

const table = useTemplateRef('table')

const columnVisibility = ref({
  id: false
})
</script>

<template>
  <div class="flex flex-col flex-1 w-full">
    <div class="flex justify-end px-4 py-3.5 border-b border-accented">
      <UDropdownMenu
        :items="
          table?.tableApi
            ?.getAllColumns()
            .filter((column) => column.getCanHide())
            .map((column) => ({
              label: upperFirst(column.id),
              type: 'checkbox' as const,
              checked: column.getIsVisible(),
              onUpdateChecked(checked: boolean) {
                table?.tableApi?.getColumn(column.id)?.toggleVisibility(!!checked)
              },
              onSelect(e: Event) {
                e.preventDefault()
              }
            }))
        "
        :content="{ align: 'end' }"
      >
        <UButton
          label="Columns"
          color="neutral"
          variant="outline"
          trailing-icon="i-lucide-chevron-down"
        />
      </UDropdownMenu>
    </div>

    <UTable
      ref="table"
      v-model:column-visibility="columnVisibility"
      :data="data"
      :columns="columns"
    />
  </div>
</template>
Sie können die column-visibility-prop verwenden, um den Sichtbarkeitsstatus der Spalten zu steuern (kann mit v-model gebunden werden).

With Spaltenfilter

Sie können eine Input-Komponente verwenden, um die Zeilen mithilfe der TanStack-Tabelle Column Filtering APIs pro Spalte zu filtern.

#DateStatusEmailAmount
#4600Mar 11, 15:30paidjames.anderson@example.com€594.00
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'

const UBadge = resolveComponent('UBadge')

type Payment = {
  id: string
  date: string
  status: 'paid' | 'failed' | 'refunded'
  email: string
  amount: number
}

const data = ref<Payment[]>([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    status: 'paid',
    email: 'james.anderson@example.com',
    amount: 594
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    status: 'failed',
    email: 'mia.white@example.com',
    amount: 276
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    status: 'refunded',
    email: 'william.brown@example.com',
    amount: 315
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    status: 'paid',
    email: 'emma.davis@example.com',
    amount: 529
  },
  {
    id: '4596',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639
  }
])

const columns: TableColumn<Payment>[] = [
  {
    accessorKey: 'id',
    header: '#',
    cell: ({ row }) => `#${row.getValue('id')}`
  },
  {
    accessorKey: 'date',
    header: 'Date',
    cell: ({ row }) => {
      return new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      })
    }
  },
  {
    accessorKey: 'status',
    header: 'Status',
    cell: ({ row }) => {
      const color = {
        paid: 'success' as const,
        failed: 'error' as const,
        refunded: 'neutral' as const
      }[row.getValue('status') as string]

      return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
        row.getValue('status')
      )
    }
  },
  {
    accessorKey: 'email',
    header: 'Email'
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(amount)
    }
  }
]

const table = useTemplateRef('table')

const columnFilters = ref([
  {
    id: 'email',
    value: 'james'
  }
])
</script>

<template>
  <div class="flex flex-col flex-1 w-full">
    <div class="flex px-4 py-3.5 border-b border-accented">
      <UInput
        :model-value="table?.tableApi?.getColumn('email')?.getFilterValue() as string"
        class="max-w-sm"
        placeholder="Filter emails..."
        @update:model-value="table?.tableApi?.getColumn('email')?.setFilterValue($event)"
      />
    </div>

    <UTable ref="table" v-model:column-filters="columnFilters" :data="data" :columns="columns" />
  </div>
</template>
<script setup lang="ts">
import { h, resolveComponent, ref, useTemplateRef } from 'vue'
import type { TableColumn } from '@nuxt/ui'

const UBadge = resolveComponent('UBadge')

type Payment = {
  id: string
  date: string
  status: 'paid' | 'failed' | 'refunded'
  email: string
  amount: number
}

const data = ref<Payment[]>([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    status: 'paid',
    email: 'james.anderson@example.com',
    amount: 594
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    status: 'failed',
    email: 'mia.white@example.com',
    amount: 276
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    status: 'refunded',
    email: 'william.brown@example.com',
    amount: 315
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    status: 'paid',
    email: 'emma.davis@example.com',
    amount: 529
  },
  {
    id: '4596',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639
  }
])

const columns: TableColumn<Payment>[] = [
  {
    accessorKey: 'id',
    header: '#',
    cell: ({ row }) => `#${row.getValue('id')}`
  },
  {
    accessorKey: 'date',
    header: 'Date',
    cell: ({ row }) => {
      return new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      })
    }
  },
  {
    accessorKey: 'status',
    header: 'Status',
    cell: ({ row }) => {
      const color = {
        paid: 'success' as const,
        failed: 'error' as const,
        refunded: 'neutral' as const
      }[row.getValue('status') as string]

      return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
        row.getValue('status')
      )
    }
  },
  {
    accessorKey: 'email',
    header: 'Email'
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(amount)
    }
  }
]

const table = useTemplateRef('table')

const columnFilters = ref([
  {
    id: 'email',
    value: 'james'
  }
])
</script>

<template>
  <div class="flex flex-col flex-1 w-full">
    <div class="flex px-4 py-3.5 border-b border-accented">
      <UInput
        :model-value="table?.tableApi?.getColumn('email')?.getFilterValue() as string"
        class="max-w-sm"
        placeholder="Filter emails..."
        @update:model-value="table?.tableApi?.getColumn('email')?.setFilterValue($event)"
      />
    </div>

    <UTable ref="table" v-model:column-filters="columnFilters" :data="data" :columns="columns" />
  </div>
</template>
Sie können die column-filters-prop verwenden, um den Filterstatus der Spalten zu steuern (kann mit v-model gebunden werden).

Mit globalem Filter

Sie können eine Input-Komponente verwenden, um die Zeilen mithilfe der TanStack-Tabelle Global Filtering APIs zu filtern.

#DateStatusEmailAmount
#4599Mar 11, 10:10failedmia.white@example.com€276.00
#4598Mar 11, 08:50refundedwilliam.brown@example.com€315.00
#4597Mar 10, 19:45paidemma.davis@example.com€529.00
#4596Mar 10, 15:55paidethan.harris@example.com€639.00
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'

const UBadge = resolveComponent('UBadge')

type Payment = {
  id: string
  date: string
  status: 'paid' | 'failed' | 'refunded'
  email: string
  amount: number
}

const data = ref<Payment[]>([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    status: 'paid',
    email: 'james.anderson@example.com',
    amount: 594
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    status: 'failed',
    email: 'mia.white@example.com',
    amount: 276
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    status: 'refunded',
    email: 'william.brown@example.com',
    amount: 315
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    status: 'paid',
    email: 'emma.davis@example.com',
    amount: 529
  },
  {
    id: '4596',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639
  }
])

const columns: TableColumn<Payment>[] = [
  {
    accessorKey: 'id',
    header: '#',
    cell: ({ row }) => `#${row.getValue('id')}`
  },
  {
    accessorKey: 'date',
    header: 'Date',
    cell: ({ row }) => {
      return new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      })
    }
  },
  {
    accessorKey: 'status',
    header: 'Status',
    cell: ({ row }) => {
      const color = {
        paid: 'success' as const,
        failed: 'error' as const,
        refunded: 'neutral' as const
      }[row.getValue('status') as string]

      return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
        row.getValue('status')
      )
    }
  },
  {
    accessorKey: 'email',
    header: 'Email'
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(amount)
    }
  }
]

const globalFilter = ref('45')
</script>

<template>
  <div class="flex flex-col flex-1 w-full">
    <div class="flex px-4 py-3.5 border-b border-accented">
      <UInput v-model="globalFilter" class="max-w-sm" placeholder="Filter..." />
    </div>

    <UTable ref="table" v-model:global-filter="globalFilter" :data="data" :columns="columns" />
  </div>
</template>
<script setup lang="ts">
import { h, resolveComponent, ref } from 'vue'
import type { TableColumn } from '@nuxt/ui'

const UBadge = resolveComponent('UBadge')

type Payment = {
  id: string
  date: string
  status: 'paid' | 'failed' | 'refunded'
  email: string
  amount: number
}

const data = ref<Payment[]>([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    status: 'paid',
    email: 'james.anderson@example.com',
    amount: 594
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    status: 'failed',
    email: 'mia.white@example.com',
    amount: 276
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    status: 'refunded',
    email: 'william.brown@example.com',
    amount: 315
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    status: 'paid',
    email: 'emma.davis@example.com',
    amount: 529
  },
  {
    id: '4596',
    date: '2024-03-10T15:55:00',
    status: 'paid',
    email: 'ethan.harris@example.com',
    amount: 639
  }
])

const columns: TableColumn<Payment>[] = [
  {
    accessorKey: 'id',
    header: '#',
    cell: ({ row }) => `#${row.getValue('id')}`
  },
  {
    accessorKey: 'date',
    header: 'Date',
    cell: ({ row }) => {
      return new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      })
    }
  },
  {
    accessorKey: 'status',
    header: 'Status',
    cell: ({ row }) => {
      const color = {
        paid: 'success' as const,
        failed: 'error' as const,
        refunded: 'neutral' as const
      }[row.getValue('status') as string]

      return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
        row.getValue('status')
      )
    }
  },
  {
    accessorKey: 'email',
    header: 'Email'
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(amount)
    }
  }
]

const globalFilter = ref('45')
</script>

<template>
  <div class="flex flex-col flex-1 w-full">
    <div class="flex px-4 py-3.5 border-b border-accented">
      <UInput v-model="globalFilter" class="max-w-sm" placeholder="Filter..." />
    </div>

    <UTable ref="table" v-model:global-filter="globalFilter" :data="data" :columns="columns" />
  </div>
</template>
Sie können die global-filter-prop verwenden, um den globalen Filterstatus zu steuern (kann mit v-model gebunden werden).

mit Paginierung

Sie können eine Pagination-Komponente verwenden, um den Paginierungsstatus mit dem Pagination-APIs zu steuern.

Es gibt verschiedene Paginierungsansätze, wie in Pagination Guide erläutert. In diesem Beispiel verwenden wir die clientseitige Paginierung, sodass wir die getPaginationRowModel()-Funktion manuell übergeben müssen.

#DateEmailAmount
#4600Mar 11, 15:30james.anderson@example.com€594.00
#4599Mar 11, 10:10mia.white@example.com€276.00
#4598Mar 11, 08:50william.brown@example.com€315.00
#4597Mar 10, 19:45emma.davis@example.com€529.00
#4596Mar 10, 15:55ethan.harris@example.com€639.00
<script setup lang="ts">
import { getPaginationRowModel } from '@tanstack/vue-table'
import type { TableColumn } from '@nuxt/ui'

const table = useTemplateRef('table')

type Payment = {
  id: string
  date: string
  email: string
  amount: number
}
const data = ref<Payment[]>([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    email: 'james.anderson@example.com',
    amount: 594
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    email: 'mia.white@example.com',
    amount: 276
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    email: 'william.brown@example.com',
    amount: 315
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    email: 'emma.davis@example.com',
    amount: 529
  },
  {
    id: '4596',
    date: '2024-03-10T15:55:00',
    email: 'ethan.harris@example.com',
    amount: 639
  },
  {
    id: '4595',
    date: '2024-03-10T13:20:00',
    email: 'sophia.miller@example.com',
    amount: 428
  },
  {
    id: '4594',
    date: '2024-03-10T11:05:00',
    email: 'noah.wilson@example.com',
    amount: 673
  },
  {
    id: '4593',
    date: '2024-03-09T22:15:00',
    email: 'olivia.jones@example.com',
    amount: 382
  },
  {
    id: '4592',
    date: '2024-03-09T20:30:00',
    email: 'liam.taylor@example.com',
    amount: 547
  },
  {
    id: '4591',
    date: '2024-03-09T18:45:00',
    email: 'ava.thomas@example.com',
    amount: 291
  },
  {
    id: '4590',
    date: '2024-03-09T16:20:00',
    email: 'lucas.martin@example.com',
    amount: 624
  },
  {
    id: '4589',
    date: '2024-03-09T14:10:00',
    email: 'isabella.clark@example.com',
    amount: 438
  },
  {
    id: '4588',
    date: '2024-03-09T12:05:00',
    email: 'mason.rodriguez@example.com',
    amount: 583
  },
  {
    id: '4587',
    date: '2024-03-09T10:30:00',
    email: 'sophia.lee@example.com',
    amount: 347
  },
  {
    id: '4586',
    date: '2024-03-09T08:15:00',
    email: 'ethan.walker@example.com',
    amount: 692
  },
  {
    id: '4585',
    date: '2024-03-08T23:40:00',
    email: 'amelia.hall@example.com',
    amount: 419
  },
  {
    id: '4584',
    date: '2024-03-08T21:25:00',
    email: 'oliver.young@example.com',
    amount: 563
  },
  {
    id: '4583',
    date: '2024-03-08T19:50:00',
    email: 'aria.king@example.com',
    amount: 328
  },
  {
    id: '4582',
    date: '2024-03-08T17:35:00',
    email: 'henry.wright@example.com',
    amount: 647
  },
  {
    id: '4581',
    date: '2024-03-08T15:20:00',
    email: 'luna.lopez@example.com',
    amount: 482
  }
])
const columns: TableColumn<Payment>[] = [
  {
    accessorKey: 'id',
    header: '#',
    cell: ({ row }) => `#${row.getValue('id')}`
  },
  {
    accessorKey: 'date',
    header: 'Date',
    cell: ({ row }) => {
      return new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      })
    }
  },
  {
    accessorKey: 'email',
    header: 'Email'
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(amount)
    }
  }
]

const pagination = ref({
  pageIndex: 0,
  pageSize: 5
})

const globalFilter = ref('')
</script>

<template>
  <div class="w-full space-y-4 pb-4">
    <div class="flex px-4 py-3.5 border-b border-accented">
      <UInput v-model="globalFilter" class="max-w-sm" placeholder="Filter..." />
    </div>

    <UTable
      ref="table"
      v-model:pagination="pagination"
      v-model:global-filter="globalFilter"
      :data="data"
      :columns="columns"
      :pagination-options="{
        getPaginationRowModel: getPaginationRowModel()
      }"
      class="flex-1"
    />

    <div class="flex justify-end border-t border-default pt-4 px-4">
      <UPagination
        :page="(table?.tableApi?.getState().pagination.pageIndex || 0) + 1"
        :items-per-page="table?.tableApi?.getState().pagination.pageSize"
        :total="table?.tableApi?.getFilteredRowModel().rows.length"
        @update:page="(p) => table?.tableApi?.setPageIndex(p - 1)"
      />
    </div>
  </div>
</template>
<script setup lang="ts">
import { ref, useTemplateRef } from 'vue'
import { getPaginationRowModel } from '@tanstack/vue-table'
import type { TableColumn } from '@nuxt/ui'

const table = useTemplateRef('table')

type Payment = {
  id: string
  date: string
  email: string
  amount: number
}
const data = ref<Payment[]>([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    email: 'james.anderson@example.com',
    amount: 594
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    email: 'mia.white@example.com',
    amount: 276
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    email: 'william.brown@example.com',
    amount: 315
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    email: 'emma.davis@example.com',
    amount: 529
  },
  {
    id: '4596',
    date: '2024-03-10T15:55:00',
    email: 'ethan.harris@example.com',
    amount: 639
  },
  {
    id: '4595',
    date: '2024-03-10T13:20:00',
    email: 'sophia.miller@example.com',
    amount: 428
  },
  {
    id: '4594',
    date: '2024-03-10T11:05:00',
    email: 'noah.wilson@example.com',
    amount: 673
  },
  {
    id: '4593',
    date: '2024-03-09T22:15:00',
    email: 'olivia.jones@example.com',
    amount: 382
  },
  {
    id: '4592',
    date: '2024-03-09T20:30:00',
    email: 'liam.taylor@example.com',
    amount: 547
  },
  {
    id: '4591',
    date: '2024-03-09T18:45:00',
    email: 'ava.thomas@example.com',
    amount: 291
  },
  {
    id: '4590',
    date: '2024-03-09T16:20:00',
    email: 'lucas.martin@example.com',
    amount: 624
  },
  {
    id: '4589',
    date: '2024-03-09T14:10:00',
    email: 'isabella.clark@example.com',
    amount: 438
  },
  {
    id: '4588',
    date: '2024-03-09T12:05:00',
    email: 'mason.rodriguez@example.com',
    amount: 583
  },
  {
    id: '4587',
    date: '2024-03-09T10:30:00',
    email: 'sophia.lee@example.com',
    amount: 347
  },
  {
    id: '4586',
    date: '2024-03-09T08:15:00',
    email: 'ethan.walker@example.com',
    amount: 692
  },
  {
    id: '4585',
    date: '2024-03-08T23:40:00',
    email: 'amelia.hall@example.com',
    amount: 419
  },
  {
    id: '4584',
    date: '2024-03-08T21:25:00',
    email: 'oliver.young@example.com',
    amount: 563
  },
  {
    id: '4583',
    date: '2024-03-08T19:50:00',
    email: 'aria.king@example.com',
    amount: 328
  },
  {
    id: '4582',
    date: '2024-03-08T17:35:00',
    email: 'henry.wright@example.com',
    amount: 647
  },
  {
    id: '4581',
    date: '2024-03-08T15:20:00',
    email: 'luna.lopez@example.com',
    amount: 482
  }
])
const columns: TableColumn<Payment>[] = [
  {
    accessorKey: 'id',
    header: '#',
    cell: ({ row }) => `#${row.getValue('id')}`
  },
  {
    accessorKey: 'date',
    header: 'Date',
    cell: ({ row }) => {
      return new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      })
    }
  },
  {
    accessorKey: 'email',
    header: 'Email'
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(amount)
    }
  }
]

const pagination = ref({
  pageIndex: 0,
  pageSize: 5
})

const globalFilter = ref('')
</script>

<template>
  <div class="w-full space-y-4 pb-4">
    <div class="flex px-4 py-3.5 border-b border-accented">
      <UInput v-model="globalFilter" class="max-w-sm" placeholder="Filter..." />
    </div>

    <UTable
      ref="table"
      v-model:pagination="pagination"
      v-model:global-filter="globalFilter"
      :data="data"
      :columns="columns"
      :pagination-options="{
        getPaginationRowModel: getPaginationRowModel()
      }"
      class="flex-1"
    />

    <div class="flex justify-end border-t border-default pt-4 px-4">
      <UPagination
        :page="(table?.tableApi?.getState().pagination.pageIndex || 0) + 1"
        :items-per-page="table?.tableApi?.getState().pagination.pageSize"
        :total="table?.tableApi?.getFilteredRowModel().rows.length"
        @update:page="(p) => table?.tableApi?.setPageIndex(p - 1)"
      />
    </div>
  </div>
</template>
Sie können die pagination prop verwenden, um den Paginierungszustand zu steuern (kann mit v-model gebunden werden).

Mit abgeholten Daten

Sie können Daten aus einer API abrufen und in der Tabelle verwenden.

IDNameEmailCompany
No data
<script setup lang="ts">
import type { AvatarProps, TableColumn } from '@nuxt/ui'

const UAvatar = resolveComponent('UAvatar')

type User = {
  id: number
  name: string
  username: string
  email: string
  avatar: AvatarProps
  company: { name: string }
}

const { data, status } = useLazyFetch<User[]>('https://jsonplaceholder.typicode.com/users', {
  key: 'table-users',
  transform: (data) => {
    return (
      data?.map((user) => ({
        ...user,
        avatar: { src: `https://i.pravatar.cc/120?img=${user.id}`, alt: `${user.name} avatar` }
      })) || []
    )
  },
  server: false
})

const columns: TableColumn<User>[] = [
  {
    accessorKey: 'id',
    header: 'ID'
  },
  {
    accessorKey: 'name',
    header: 'Name',
    cell: ({ row }) => {
      return h('div', { class: 'flex items-center gap-3' }, [
        h(UAvatar, {
          ...row.original.avatar,
          loading: 'lazy',
          size: 'lg'
        }),
        h('div', undefined, [
          h('p', { class: 'font-medium text-highlighted' }, row.original.name),
          h('p', { class: '' }, `@${row.original.username}`)
        ])
      ])
    }
  },
  {
    accessorKey: 'email',
    header: 'Email'
  },
  {
    accessorKey: 'company',
    header: 'Company',
    cell: ({ row }) => row.original.company.name
  }
]
</script>

<template>
  <UTable
    :data="data"
    :columns="columns"
    :loading="status === 'pending' || status === 'idle'"
    class="flex-1 h-80"
  />
</template>
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { AvatarProps, TableColumn } from '@nuxt/ui'

const UAvatar = resolveComponent('UAvatar')

type User = {
  id: number
  name: string
  username: string
  email: string
  avatar: AvatarProps
  company: { name: string }
}

const { data, status } = useLazyFetch<User[]>('https://jsonplaceholder.typicode.com/users', {
  key: 'table-users',
  transform: (data) => {
    return (
      data?.map((user) => ({
        ...user,
        avatar: { src: `https://i.pravatar.cc/120?img=${user.id}`, alt: `${user.name} avatar` }
      })) || []
    )
  },
  server: false
})

const columns: TableColumn<User>[] = [
  {
    accessorKey: 'id',
    header: 'ID'
  },
  {
    accessorKey: 'name',
    header: 'Name',
    cell: ({ row }) => {
      return h('div', { class: 'flex items-center gap-3' }, [
        h(UAvatar, {
          ...row.original.avatar,
          loading: 'lazy',
          size: 'lg'
        }),
        h('div', undefined, [
          h('p', { class: 'font-medium text-highlighted' }, row.original.name),
          h('p', { class: '' }, `@${row.original.username}`)
        ])
      ])
    }
  },
  {
    accessorKey: 'email',
    header: 'Email'
  },
  {
    accessorKey: 'company',
    header: 'Company',
    cell: ({ row }) => row.original.company.name
  }
]
</script>

<template>
  <UTable
    :data="data"
    :columns="columns"
    :loading="status === 'pending' || status === 'idle'"
    class="flex-1 h-80"
  />
</template>
In diesem Beispiel wird useLazyFetch mit server: false verwendet, um Daten auf dem Client abzurufen, ohne das anfängliche Rendern zu blockieren. Der Ladezustand überprüft sowohl den pending-als auch den idle-Status, um vor und während des Abrufs einen Ladeindikator anzuzeigen.

Mit unendlichem Scrollen

Wenn Sie die serverseitige Paginierung verwenden, können Sie das Composable useInfiniteScroll verwenden, um mehr Daten zu laden, wenn der Benutzer scrollt.

IDAvatarFirst nameEmailUsername
No data
<script setup lang="ts">
import type { TableColumn } from '@nuxt/ui'
import { useInfiniteScroll } from '@vueuse/core'

const UAvatar = resolveComponent('UAvatar')

type User = {
  id: number
  firstName: string
  username: string
  email: string
  image: string
}

type UserResponse = {
  users: User[]
  total: number
  skip: number
  limit: number
}

const skip = ref(0)

const { data, status } = useLazyFetch(
  'https://dummyjson.com/users?limit=10&select=firstName,username,email,image',
  {
    key: 'table-users-infinite-scroll',
    params: { skip },
    transform: (data?: UserResponse) => {
      return data?.users
    },
    server: false
  }
)

const columns: TableColumn<User>[] = [
  {
    accessorKey: 'id',
    header: 'ID'
  },
  {
    accessorKey: 'image',
    header: 'Avatar',
    cell: ({ row }) => h(UAvatar, { src: row.original.image, loading: 'lazy' })
  },
  {
    accessorKey: 'firstName',
    header: 'First name'
  },
  {
    accessorKey: 'email',
    header: 'Email'
  },
  {
    accessorKey: 'username',
    header: 'Username'
  }
]

const users = ref<User[]>([])

watch(data, () => {
  users.value = [...users.value, ...(data.value || [])]
})

const table = useTemplateRef('table')

onMounted(() => {
  useInfiniteScroll(
    table.value?.$el,
    () => {
      skip.value += 10
    },
    {
      distance: 200,
      canLoadMore: () => {
        return status.value !== 'pending'
      }
    }
  )
})
</script>

<template>
  <UTable
    ref="table"
    :data="users"
    :columns="columns"
    :loading="status === 'pending' || status === 'idle'"
    sticky
    class="flex-1 h-80"
  />
</template>
<script setup lang="ts">
import { ref, watch, onMounted, h, resolveComponent, useTemplateRef } from 'vue'
import type { TableColumn } from '@nuxt/ui'
import { useInfiniteScroll } from '@vueuse/core'

const UAvatar = resolveComponent('UAvatar')

type User = {
  id: number
  firstName: string
  username: string
  email: string
  image: string
}

type UserResponse = {
  users: User[]
  total: number
  skip: number
  limit: number
}

const skip = ref(0)

const { data, status } = useLazyFetch(
  'https://dummyjson.com/users?limit=10&select=firstName,username,email,image',
  {
    key: 'table-users-infinite-scroll',
    params: { skip },
    transform: (data?: UserResponse) => {
      return data?.users
    },
    server: false
  }
)

const columns: TableColumn<User>[] = [
  {
    accessorKey: 'id',
    header: 'ID'
  },
  {
    accessorKey: 'image',
    header: 'Avatar',
    cell: ({ row }) => h(UAvatar, { src: row.original.image, loading: 'lazy' })
  },
  {
    accessorKey: 'firstName',
    header: 'First name'
  },
  {
    accessorKey: 'email',
    header: 'Email'
  },
  {
    accessorKey: 'username',
    header: 'Username'
  }
]

const users = ref<User[]>([])

watch(data, () => {
  users.value = [...users.value, ...(data.value || [])]
})

const table = useTemplateRef('table')

onMounted(() => {
  useInfiniteScroll(
    table.value?.$el,
    () => {
      skip.value += 10
    },
    {
      distance: 200,
      canLoadMore: () => {
        return status.value !== 'pending'
      }
    }
  )
})
</script>

<template>
  <UTable
    ref="table"
    :data="users"
    :columns="columns"
    :loading="status === 'pending' || status === 'idle'"
    sticky
    class="flex-1 h-80"
  />
</template>
In diesem Beispiel wird useLazyFetch mit server: false verwendet, um Daten auf dem Client abzurufen, ohne das anfängliche Rendern zu blockieren. Der Ladezustand überprüft sowohl den pending-als auch den idle-Status, um eine Ladeanzeige vor und während des Abrufs anzuzeigen. Zusätzliche Seiten werden geladen, wenn der Benutzer scrollt.

Mit Drag and Drop

Sie können die useSortable composable von @vueuse/integrations verwenden, um die Drag & Drop-Funktionalität auf der Tabelle zu aktivieren.

Da die Tabellenref das tbody-Element nicht verfügbar macht, fügen Sie ihm über die :ui-Prop eine eindeutige Klasse hinzu, um es mit useSortable (z. B. :ui="{ tbody: 'my-table-tbody' }") anzusprechen.
#DateEmailAmount
#4600Mar 11, 15:30james.anderson@example.com€594.00
#4599Mar 11, 10:10mia.white@example.com€276.00
#4598Mar 11, 08:50william.brown@example.com€315.00
#4597Mar 10, 19:45emma.davis@example.com€529.00
<script setup lang="ts">
import type { TableColumn } from '@nuxt/ui'
import { useSortable } from '@vueuse/integrations/useSortable'

type Payment = {
  id: string
  date: string
  email: string
  amount: number
}

const data = ref<Payment[]>([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    email: 'james.anderson@example.com',
    amount: 594
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    email: 'mia.white@example.com',
    amount: 276
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    email: 'william.brown@example.com',
    amount: 315
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    email: 'emma.davis@example.com',
    amount: 529
  }
])

const columns: TableColumn<Payment>[] = [
  {
    accessorKey: 'id',
    header: '#',
    cell: ({ row }) => `#${row.getValue('id')}`
  },
  {
    accessorKey: 'date',
    header: 'Date',
    cell: ({ row }) => {
      return new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      })
    }
  },
  {
    accessorKey: 'email',
    header: 'Email'
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(amount)
    }
  }
]

useSortable('.my-table-tbody', data, {
  animation: 150
})
</script>

<template>
  <UTable
    ref="table"
    :data="data"
    :columns="columns"
    :ui="{
      tbody: 'my-table-tbody'
    }"
    class="flex-1"
  />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { TableColumn } from '@nuxt/ui'
import { useSortable } from '@vueuse/integrations/useSortable'

type Payment = {
  id: string
  date: string
  email: string
  amount: number
}

const data = ref<Payment[]>([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    email: 'james.anderson@example.com',
    amount: 594
  },
  {
    id: '4599',
    date: '2024-03-11T10:10:00',
    email: 'mia.white@example.com',
    amount: 276
  },
  {
    id: '4598',
    date: '2024-03-11T08:50:00',
    email: 'william.brown@example.com',
    amount: 315
  },
  {
    id: '4597',
    date: '2024-03-10T19:45:00',
    email: 'emma.davis@example.com',
    amount: 529
  }
])

const columns: TableColumn<Payment>[] = [
  {
    accessorKey: 'id',
    header: '#',
    cell: ({ row }) => `#${row.getValue('id')}`
  },
  {
    accessorKey: 'date',
    header: 'Date',
    cell: ({ row }) => {
      return new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      })
    }
  },
  {
    accessorKey: 'email',
    header: 'Email'
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(amount)
    }
  }
]

useSortable('.my-table-tbody', data, {
  animation: 150
})
</script>

<template>
  <UTable
    ref="table"
    :data="data"
    :columns="columns"
    :ui="{
      tbody: 'my-table-tbody'
    }"
    class="flex-1"
  />
</template>

Virtualisierung: badge{label="4.1+" class="align-text-top"}

Verwenden Sie die virtualize-prop, um die Virtualisierung für große Datensätze als Boolean oder ein Objekt mit Optionen wie { estimateSize: 65, overscan: 12 } zu aktivieren. Sie können auch andere TanStack Virtual options übergeben, um das Virtualisierungsverhalten anzupassen.

Zeilenanheften wird nicht unterstützt, wenn die Virtualisierung aktiviert ist.
#DateStatusEmailAmount
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'

const UBadge = resolveComponent('UBadge')

type Payment = {
  id: string
  date: string
  status: 'paid' | 'failed' | 'refunded'
  email: string
  amount: number
}

const data = ref<Payment[]>(
  Array(1000)
    .fill(0)
    .map((_, i) => ({
      id: `4600-${i}`,
      date: '2024-03-11T15:30:00',
      status: 'paid',
      email: 'james.anderson@example.com',
      amount: 594
    }))
)

const columns: TableColumn<Payment>[] = [
  {
    accessorKey: 'id',
    header: '#',
    cell: ({ row }) => `#${row.getValue('id')}`
  },
  {
    accessorKey: 'date',
    header: 'Date',
    cell: ({ row }) => {
      return new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      })
    }
  },
  {
    accessorKey: 'status',
    header: 'Status',
    cell: ({ row }) => {
      const color = {
        paid: 'success' as const,
        failed: 'error' as const,
        refunded: 'neutral' as const
      }[row.getValue('status') as string]

      return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
        row.getValue('status')
      )
    }
  },
  {
    accessorKey: 'email',
    header: 'Email'
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(amount)
    }
  }
]
</script>

<template>
  <UTable sticky virtualize :data="data" :columns="columns" class="flex-1 h-80" />
</template>
<script setup lang="ts">
import { h, resolveComponent, ref } from 'vue'
import type { TableColumn } from '@nuxt/ui'

const UBadge = resolveComponent('UBadge')

type Payment = {
  id: string
  date: string
  status: 'paid' | 'failed' | 'refunded'
  email: string
  amount: number
}

const data = ref<Payment[]>(
  Array(1000)
    .fill(0)
    .map((_, i) => ({
      id: `4600-${i}`,
      date: '2024-03-11T15:30:00',
      status: 'paid',
      email: 'james.anderson@example.com',
      amount: 594
    }))
)

const columns: TableColumn<Payment>[] = [
  {
    accessorKey: 'id',
    header: '#',
    cell: ({ row }) => `#${row.getValue('id')}`
  },
  {
    accessorKey: 'date',
    header: 'Date',
    cell: ({ row }) => {
      return new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      })
    }
  },
  {
    accessorKey: 'status',
    header: 'Status',
    cell: ({ row }) => {
      const color = {
        paid: 'success' as const,
        failed: 'error' as const,
        refunded: 'neutral' as const
      }[row.getValue('status') as string]

      return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
        row.getValue('status')
      )
    }
  },
  {
    accessorKey: 'email',
    header: 'Email'
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(amount)
    }
  }
]
</script>

<template>
  <UTable sticky virtualize :data="data" :columns="columns" class="flex-1 h-80" />
</template>
Eine Höhenbeschränkung ist auf der Tabelle erforderlich, damit die Virtualisierung ordnungsgemäß funktioniert (z. B. class="h-[400px]").

Mit externem Scrollelement: badge{label="4.10+" class="align-text-top"}

Übergeben Sie eine getScrollElement-Funktion in der virtualize-Prop, um gegen einen Vorfahren-Scroll-Container anstelle des tabelleneigenen Root zu virtualisieren. Setzen Sie scrollMargin auf den Offset der Tabelle vom Start des Scroll-Elements (z. B. die Höhe des Inhalts darüber), so dass ein Header und der Tabellenkörper eine einzige Scrollleiste teilen.

Payments

The title stays put while the wide table scrolls both axes under one scrollbar.

1000 rows
#DateCustomerEmailMethodStatusAmount
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'

const UBadge = resolveComponent('UBadge')

type Payment = {
  id: string
  date: string
  customer: string
  email: string
  method: string
  status: 'paid' | 'failed' | 'refunded'
  amount: number
}

const names = [
  'James Anderson',
  'Mary Johnson',
  'Robert Williams',
  'Patricia Brown',
  'Michael Davis'
]
const methods = ['Visa', 'Mastercard', 'PayPal', 'Amex']
const statuses = ['paid', 'failed', 'refunded'] as const

const data = ref<Payment[]>(
  Array.from({ length: 1000 }, (_, i) => {
    const customer = names[i % names.length]!
    return {
      id: `4600-${i}`,
      date: '2024-03-11T15:30:00',
      customer,
      email: `${customer.toLowerCase().replace(' ', '.')}@example.com`,
      method: methods[i % methods.length]!,
      status: statuses[i % statuses.length]!,
      amount: 100 + ((i * 37) % 900)
    }
  })
)

// The container owns the scroll; the table virtualizes against it so the header and rows share one scrollbar.
const container = useTemplateRef('container')
const title = useTemplateRef('title')
const getScrollElement = () => container.value

// `scrollMargin` is the table's offset within the scroll element (border-box height of the title above it).
const { height: titleHeight } = useElementSize(title, undefined, { box: 'border-box' })

const columns: TableColumn<Payment>[] = [
  {
    accessorKey: 'id',
    header: '#',
    cell: ({ row }) => `#${row.getValue('id')}`
  },
  {
    accessorKey: 'date',
    header: 'Date',
    cell: ({ row }) =>
      new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      })
  },
  {
    accessorKey: 'customer',
    header: 'Customer'
  },
  {
    accessorKey: 'email',
    header: 'Email'
  },
  {
    accessorKey: 'method',
    header: 'Method'
  },
  {
    accessorKey: 'status',
    header: 'Status',
    cell: ({ row }) => {
      const color = {
        paid: 'success' as const,
        failed: 'error' as const,
        refunded: 'neutral' as const
      }[row.getValue('status') as string]

      return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
        row.getValue('status')
      )
    }
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    cell: ({ row }) =>
      new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(Number.parseFloat(row.getValue('amount')))
  }
]
</script>

<template>
  <div ref="container" class="w-full h-96 overflow-auto">
    <div
      ref="title"
      class="sticky left-0 z-10 flex items-end justify-between gap-4 p-6 bg-elevated/50"
    >
      <div>
        <h2 class="text-2xl font-bold text-highlighted">Payments</h2>
        <p class="text-muted">
          The title stays put while the wide table scrolls both axes under one scrollbar.
        </p>
      </div>
      <UBadge color="neutral" variant="subtle" :label="`${data.length} rows`" />
    </div>

    <UTable
      sticky
      :data="data"
      :columns="columns"
      :virtualize="{ scrollMargin: titleHeight, getScrollElement }"
      :ui="{ base: 'min-w-[1200px]' }"
    />
  </div>
</template>
<script setup lang="ts">
import { useElementSize } from '@vueuse/core'
import { h, resolveComponent, ref, useTemplateRef } from 'vue'
import type { TableColumn } from '@nuxt/ui'

const UBadge = resolveComponent('UBadge')

type Payment = {
  id: string
  date: string
  customer: string
  email: string
  method: string
  status: 'paid' | 'failed' | 'refunded'
  amount: number
}

const names = [
  'James Anderson',
  'Mary Johnson',
  'Robert Williams',
  'Patricia Brown',
  'Michael Davis'
]
const methods = ['Visa', 'Mastercard', 'PayPal', 'Amex']
const statuses = ['paid', 'failed', 'refunded'] as const

const data = ref<Payment[]>(
  Array.from({ length: 1000 }, (_, i) => {
    const customer = names[i % names.length]!
    return {
      id: `4600-${i}`,
      date: '2024-03-11T15:30:00',
      customer,
      email: `${customer.toLowerCase().replace(' ', '.')}@example.com`,
      method: methods[i % methods.length]!,
      status: statuses[i % statuses.length]!,
      amount: 100 + ((i * 37) % 900)
    }
  })
)

// The container owns the scroll; the table virtualizes against it so the header and rows share one scrollbar.
const container = useTemplateRef('container')
const title = useTemplateRef('title')
const getScrollElement = () => container.value

// `scrollMargin` is the table's offset within the scroll element (border-box height of the title above it).
const { height: titleHeight } = useElementSize(title, undefined, { box: 'border-box' })

const columns: TableColumn<Payment>[] = [
  {
    accessorKey: 'id',
    header: '#',
    cell: ({ row }) => `#${row.getValue('id')}`
  },
  {
    accessorKey: 'date',
    header: 'Date',
    cell: ({ row }) =>
      new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      })
  },
  {
    accessorKey: 'customer',
    header: 'Customer'
  },
  {
    accessorKey: 'email',
    header: 'Email'
  },
  {
    accessorKey: 'method',
    header: 'Method'
  },
  {
    accessorKey: 'status',
    header: 'Status',
    cell: ({ row }) => {
      const color = {
        paid: 'success' as const,
        failed: 'error' as const,
        refunded: 'neutral' as const
      }[row.getValue('status') as string]

      return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
        row.getValue('status')
      )
    }
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    cell: ({ row }) =>
      new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(Number.parseFloat(row.getValue('amount')))
  }
]
</script>

<template>
  <div ref="container" class="w-full h-96 overflow-auto">
    <div
      ref="title"
      class="sticky left-0 z-10 flex items-end justify-between gap-4 p-6 bg-elevated/50"
    >
      <div>
        <h2 class="text-2xl font-bold text-highlighted">Payments</h2>
        <p class="text-muted">
          The title stays put while the wide table scrolls both axes under one scrollbar.
        </p>
      </div>
      <UBadge color="neutral" variant="subtle" :label="`${data.length} rows`" />
    </div>

    <UTable
      sticky
      :data="data"
      :columns="columns"
      :virtualize="{ scrollMargin: titleHeight, getScrollElement }"
      :ui="{ base: 'min-w-[1200px]' }"
    />
  </div>
</template>
In diesem Modus ist der overflow der Tabellenwurzel visible und der externe Container besitzt das Scrollen auf beiden Achsen, also geben Sie ihm overflow-auto (nicht nur overflow-y-auto), um breite Tabellen horizontal scrollbar zu halten.

Mit Baumdaten

Sie können die get-sub-rows-prop verwenden, um hierarchische (Baum-) Daten in der Tabelle anzuzeigen. Wenn Ihre Datenobjekte beispielsweise ein children-Array haben, legen Sie :get-sub-rows="row => row.children" so fest, dass erweiterbare Zeilen aktiviert werden.

#DateEmailAmount
4600
Mar 11, 15:30james.anderson@example.com€594.00
4599
Mar 11, 10:10mia.white@example.com€276.00
4598
Mar 11, 08:50william.brown@example.com€315.00
4597
Mar 10, 19:45emma.davis@example.com€529.00
4589
Mar 9, 11:35isabella.lee@example.com€389.00
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'

const UCheckbox = resolveComponent('UCheckbox')
const UButton = resolveComponent('UButton')

type Payment = {
  id: string
  date: string
  email: string
  amount: number
  children?: Payment[]
}

const data = ref<Payment[]>([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    email: 'james.anderson@example.com',
    amount: 594,
    children: [
      {
        id: '4599',
        date: '2024-03-11T10:10:00',
        email: 'mia.white@example.com',
        amount: 276
      },
      {
        id: '4598',
        date: '2024-03-11T08:50:00',
        email: 'william.brown@example.com',
        amount: 315
      },
      {
        id: '4597',
        date: '2024-03-10T19:45:00',
        email: 'emma.davis@example.com',
        amount: 529,
        children: [
          {
            id: '4592',
            date: '2024-03-09T18:45:00',
            email: 'benjamin.jackson@example.com',
            amount: 851
          },
          {
            id: '4591',
            date: '2024-03-09T16:05:00',
            email: 'sophia.miller@example.com',
            amount: 762
          },
          {
            id: '4590',
            date: '2024-03-09T14:20:00',
            email: 'noah.clark@example.com',
            amount: 573,
            children: [
              {
                id: '4596',
                date: '2024-03-10T15:55:00',
                email: 'ethan.harris@example.com',
                amount: 639
              },
              {
                id: '4595',
                date: '2024-03-10T13:40:00',
                email: 'ava.thomas@example.com',
                amount: 428
              }
            ]
          }
        ]
      }
    ]
  },
  {
    id: '4589',
    date: '2024-03-09T11:35:00',
    email: 'isabella.lee@example.com',
    amount: 389
  }
])

const columns: TableColumn<Payment>[] = [
  {
    id: 'select',
    header: ({ table }) =>
      h(UCheckbox, {
        modelValue: table.getIsSomePageRowsSelected()
          ? 'indeterminate'
          : table.getIsAllPageRowsSelected(),
        'onUpdate:modelValue': (value: boolean | 'indeterminate') =>
          table.toggleAllPageRowsSelected(!!value),
        'aria-label': 'Select all'
      }),
    cell: ({ row }) =>
      h(UCheckbox, {
        modelValue: row.getIsSelected() ? true : row.getIsSomeSelected() ? 'indeterminate' : false,
        'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
        'aria-label': 'Select row'
      })
  },
  {
    accessorKey: 'id',
    header: '#',
    cell: ({ row }) => {
      return h(
        'div',
        {
          style: {
            paddingInlineStart: `${row.depth}rem`
          },
          class: 'flex items-center gap-2'
        },
        [
          h(UButton, {
            color: 'neutral',
            variant: 'outline',
            size: 'xs',
            icon: row.getIsExpanded() ? 'i-lucide-minus' : 'i-lucide-plus',
            class: !row.getCanExpand() && 'invisible',
            ui: {
              base: 'p-0 rounded-sm',
              leadingIcon: 'size-4'
            },
            onClick: row.getToggleExpandedHandler()
          }),
          row.getValue('id') as string
        ]
      )
    }
  },
  {
    accessorKey: 'date',
    header: 'Date',
    cell: ({ row }) => {
      return new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      })
    }
  },
  {
    accessorKey: 'email',
    header: 'Email'
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(amount)
    }
  }
]

const expanded = ref({ 0: true })
</script>

<template>
  <UTable
    v-model:expanded="expanded"
    :data="data"
    :columns="columns"
    :get-sub-rows="(row) => row.children"
    class="flex-1"
    :ui="{
      base: 'border-separate border-spacing-0',
      tbody: '[&>tr]:last:[&>td]:border-b-0',
      tr: 'group',
      td: 'empty:p-0 group-has-[td:not(:empty)]:border-b border-default'
    }"
  />
</template>
<script setup lang="ts">
import { h, resolveComponent, ref } from 'vue'
import type { TableColumn } from '@nuxt/ui'

const UCheckbox = resolveComponent('UCheckbox')
const UButton = resolveComponent('UButton')

type Payment = {
  id: string
  date: string
  email: string
  amount: number
  children?: Payment[]
}

const data = ref<Payment[]>([
  {
    id: '4600',
    date: '2024-03-11T15:30:00',
    email: 'james.anderson@example.com',
    amount: 594,
    children: [
      {
        id: '4599',
        date: '2024-03-11T10:10:00',
        email: 'mia.white@example.com',
        amount: 276
      },
      {
        id: '4598',
        date: '2024-03-11T08:50:00',
        email: 'william.brown@example.com',
        amount: 315
      },
      {
        id: '4597',
        date: '2024-03-10T19:45:00',
        email: 'emma.davis@example.com',
        amount: 529,
        children: [
          {
            id: '4592',
            date: '2024-03-09T18:45:00',
            email: 'benjamin.jackson@example.com',
            amount: 851
          },
          {
            id: '4591',
            date: '2024-03-09T16:05:00',
            email: 'sophia.miller@example.com',
            amount: 762
          },
          {
            id: '4590',
            date: '2024-03-09T14:20:00',
            email: 'noah.clark@example.com',
            amount: 573,
            children: [
              {
                id: '4596',
                date: '2024-03-10T15:55:00',
                email: 'ethan.harris@example.com',
                amount: 639
              },
              {
                id: '4595',
                date: '2024-03-10T13:40:00',
                email: 'ava.thomas@example.com',
                amount: 428
              }
            ]
          }
        ]
      }
    ]
  },
  {
    id: '4589',
    date: '2024-03-09T11:35:00',
    email: 'isabella.lee@example.com',
    amount: 389
  }
])

const columns: TableColumn<Payment>[] = [
  {
    id: 'select',
    header: ({ table }) =>
      h(UCheckbox, {
        modelValue: table.getIsSomePageRowsSelected()
          ? 'indeterminate'
          : table.getIsAllPageRowsSelected(),
        'onUpdate:modelValue': (value: boolean | 'indeterminate') =>
          table.toggleAllPageRowsSelected(!!value),
        'aria-label': 'Select all'
      }),
    cell: ({ row }) =>
      h(UCheckbox, {
        modelValue: row.getIsSelected() ? true : row.getIsSomeSelected() ? 'indeterminate' : false,
        'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
        'aria-label': 'Select row'
      })
  },
  {
    accessorKey: 'id',
    header: '#',
    cell: ({ row }) => {
      return h(
        'div',
        {
          style: {
            paddingInlineStart: `${row.depth}rem`
          },
          class: 'flex items-center gap-2'
        },
        [
          h(UButton, {
            color: 'neutral',
            variant: 'outline',
            size: 'xs',
            icon: row.getIsExpanded() ? 'i-lucide-minus' : 'i-lucide-plus',
            class: !row.getCanExpand() && 'invisible',
            ui: {
              base: 'p-0 rounded-sm',
              leadingIcon: 'size-4'
            },
            onClick: row.getToggleExpandedHandler()
          }),
          row.getValue('id') as string
        ]
      )
    }
  },
  {
    accessorKey: 'date',
    header: 'Date',
    cell: ({ row }) => {
      return new Date(row.getValue('date')).toLocaleString('en-US', {
        day: 'numeric',
        month: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      })
    }
  },
  {
    accessorKey: 'email',
    header: 'Email'
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: {
      class: {
        th: 'text-right',
        td: 'text-right font-medium'
      }
    },
    cell: ({ row }) => {
      const amount = Number.parseFloat(row.getValue('amount'))
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'EUR'
      }).format(amount)
    }
  }
]

const expanded = ref({ 0: true })
</script>

<template>
  <UTable
    v-model:expanded="expanded"
    :data="data"
    :columns="columns"
    :get-sub-rows="(row) => row.children"
    class="flex-1"
    :ui="{
      base: 'border-separate border-spacing-0',
      tbody: '[&>tr]:last:[&>td]:border-b-0',
      tr: 'group',
      td: 'empty:p-0 group-has-[td:not(:empty)]:border-b border-default'
    }"
  />
</template>

Mit Steckplätzen

Sie können Slots verwenden, um die Kopfzeile und die Datenzellen der Tabelle anzupassen.

Verwenden Sie den #<column>-header-Steckplatz, um die Kopfzeile einer Spalte anzupassen. Sie haben Zugriff auf die Eigenschaften column, header und table im Slotbereich.

Verwenden Sie den #<column>-cell-Steckplatz, um die Zelle einer Spalte anzupassen. Sie haben Zugriff auf die Eigenschaften cell, column, getValue, renderValue, row und table im Slotbereich.

IDNameEmailRole
1
Lindsay Walton avatar

Lindsay Walton

Front-end Developer

lindsay.walton@example.comMember
2
Courtney Henry avatar

Courtney Henry

Designer

courtney.henry@example.comAdmin
3
Tom Cook avatar

Tom Cook

Director of Product

tom.cook@example.comMember
4
Whitney Francis avatar

Whitney Francis

Copywriter

whitney.francis@example.comAdmin
5
Leonard Krasner avatar

Leonard Krasner

Senior Designer

leonard.krasner@example.comOwner
6
Floyd Miles avatar

Floyd Miles

Principal Designer

floyd.miles@example.comMember
<script setup lang="ts">
import type { TableColumn, DropdownMenuItem } from '@nuxt/ui'
import { useClipboard } from '@vueuse/core'

interface User {
  id: number
  name: string
  position: string
  email: string
  role: string
}

const toast = useToast()
const { copy } = useClipboard()

const data = ref<User[]>([
  {
    id: 1,
    name: 'Lindsay Walton',
    position: 'Front-end Developer',
    email: 'lindsay.walton@example.com',
    role: 'Member'
  },
  {
    id: 2,
    name: 'Courtney Henry',
    position: 'Designer',
    email: 'courtney.henry@example.com',
    role: 'Admin'
  },
  {
    id: 3,
    name: 'Tom Cook',
    position: 'Director of Product',
    email: 'tom.cook@example.com',
    role: 'Member'
  },
  {
    id: 4,
    name: 'Whitney Francis',
    position: 'Copywriter',
    email: 'whitney.francis@example.com',
    role: 'Admin'
  },
  {
    id: 5,
    name: 'Leonard Krasner',
    position: 'Senior Designer',
    email: 'leonard.krasner@example.com',
    role: 'Owner'
  },
  {
    id: 6,
    name: 'Floyd Miles',
    position: 'Principal Designer',
    email: 'floyd.miles@example.com',
    role: 'Member'
  }
])

const columns: TableColumn<User>[] = [
  {
    accessorKey: 'id',
    header: 'ID'
  },
  {
    accessorKey: 'name',
    header: 'Name'
  },
  {
    accessorKey: 'email',
    header: 'Email'
  },
  {
    accessorKey: 'role',
    header: 'Role'
  },
  {
    id: 'action'
  }
]

function getDropdownActions(user: User): DropdownMenuItem[][] {
  return [
    [
      {
        label: 'Copy user Id',
        icon: 'i-lucide-copy',
        onSelect: () => {
          copy(user.id.toString())

          toast.add({
            title: 'User ID copied to clipboard!',
            color: 'success',
            icon: 'i-lucide-circle-check'
          })
        }
      }
    ],
    [
      {
        label: 'Edit',
        icon: 'i-lucide-edit'
      },
      {
        label: 'Delete',
        icon: 'i-lucide-trash',
        color: 'error'
      }
    ]
  ]
}
</script>

<template>
  <UTable :data="data" :columns="columns" class="flex-1">
    <template #name-cell="{ row }">
      <div class="flex items-center gap-3">
        <UAvatar
          :src="`https://i.pravatar.cc/120?img=${row.original.id}`"
          size="lg"
          loading="lazy"
          :alt="`${row.original.name} avatar`"
        />
        <div>
          <p class="font-medium text-highlighted">
            {{ row.original.name }}
          </p>
          <p>
            {{ row.original.position }}
          </p>
        </div>
      </div>
    </template>
    <template #action-cell="{ row }">
      <UDropdownMenu :items="getDropdownActions(row.original)">
        <UButton
          icon="i-lucide-ellipsis-vertical"
          color="neutral"
          variant="ghost"
          aria-label="Actions"
        />
      </UDropdownMenu>
    </template>
  </UTable>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { TableColumn, DropdownMenuItem } from '@nuxt/ui'
import { useClipboard } from '@vueuse/core'

interface User {
  id: number
  name: string
  position: string
  email: string
  role: string
}

const toast = useToast()
const { copy } = useClipboard()

const data = ref<User[]>([
  {
    id: 1,
    name: 'Lindsay Walton',
    position: 'Front-end Developer',
    email: 'lindsay.walton@example.com',
    role: 'Member'
  },
  {
    id: 2,
    name: 'Courtney Henry',
    position: 'Designer',
    email: 'courtney.henry@example.com',
    role: 'Admin'
  },
  {
    id: 3,
    name: 'Tom Cook',
    position: 'Director of Product',
    email: 'tom.cook@example.com',
    role: 'Member'
  },
  {
    id: 4,
    name: 'Whitney Francis',
    position: 'Copywriter',
    email: 'whitney.francis@example.com',
    role: 'Admin'
  },
  {
    id: 5,
    name: 'Leonard Krasner',
    position: 'Senior Designer',
    email: 'leonard.krasner@example.com',
    role: 'Owner'
  },
  {
    id: 6,
    name: 'Floyd Miles',
    position: 'Principal Designer',
    email: 'floyd.miles@example.com',
    role: 'Member'
  }
])

const columns: TableColumn<User>[] = [
  {
    accessorKey: 'id',
    header: 'ID'
  },
  {
    accessorKey: 'name',
    header: 'Name'
  },
  {
    accessorKey: 'email',
    header: 'Email'
  },
  {
    accessorKey: 'role',
    header: 'Role'
  },
  {
    id: 'action'
  }
]

function getDropdownActions(user: User): DropdownMenuItem[][] {
  return [
    [
      {
        label: 'Copy user Id',
        icon: 'i-lucide-copy',
        onSelect: () => {
          copy(user.id.toString())

          toast.add({
            title: 'User ID copied to clipboard!',
            color: 'success',
            icon: 'i-lucide-circle-check'
          })
        }
      }
    ],
    [
      {
        label: 'Edit',
        icon: 'i-lucide-edit'
      },
      {
        label: 'Delete',
        icon: 'i-lucide-trash',
        color: 'error'
      }
    ]
  ]
}
</script>

<template>
  <UTable :data="data" :columns="columns" class="flex-1">
    <template #name-cell="{ row }">
      <div class="flex items-center gap-3">
        <UAvatar
          :src="`https://i.pravatar.cc/120?img=${row.original.id}`"
          size="lg"
          loading="lazy"
          :alt="`${row.original.name} avatar`"
        />
        <div>
          <p class="font-medium text-highlighted">
            {{ row.original.name }}
          </p>
          <p>
            {{ row.original.position }}
          </p>
        </div>
      </div>
    </template>
    <template #action-cell="{ row }">
      <UDropdownMenu :items="getDropdownActions(row.original)">
        <UButton
          icon="i-lucide-ellipsis-vertical"
          color="neutral"
          variant="ghost"
          aria-label="Actions"
        />
      </UDropdownMenu>
    </template>
  </UTable>
</template>

API (englisch)

Props (englisch)

Prop Default Type
as
'div'
any

The element or component this component should render as.

data
T[]
columns
TableColumn<T, unknown>[]
caption
string
meta
TableMeta<T>

You can pass any object to options.meta and access it anywhere the table is available via table.options.meta.

virtualize
false
boolean | (Partial<Omit<VirtualizerOptions<Element, Element>, "count" | "estimateSize" | "overscan">> & { getScrollElement?: (() => Element | null) ; overscan?: number | undefined; estimateSize?: number | ((index: number) => number) | undefined; }) | undefined

Enable virtualization for large datasets. Note: row pinning is not supported when virtualization is enabled.

empty
t('table.noData')
string

The text to display when the table is empty.

sticky
false
boolean | "header" | "footer"

Whether the table should have a sticky header or footer. True for both, 'header' for header only, 'footer' for footer only.

loading
boolean

Whether the table should be in loading state.

loadingColor
'primary'
"primary" | "secondary" | "success" | "info" | "warning" | "error" | "neutral"
loadingAnimation
'carousel'
"carousel" | "carousel-inverse" | "swing" | "elastic"
watchOptions
{ deep: true }
WatchOptions<boolean>

Use the watchOptions prop to customize reactivity (for ex: disable deep watching for changes in your data or limiting the max traversal depth). This can improve performance by reducing unnecessary re-renders, but it should be used with caution as it may lead to unexpected behavior if not managed properly.

globalFilterOptions
Omit<GlobalFilterOptions<T>, "onGlobalFilterChange">
columnFiltersOptions
Omit<ColumnFiltersOptions<T>, "getFilteredRowModel" | "onColumnFiltersChange">
columnPinningOptions
Omit<ColumnPinningOptions, "onColumnPinningChange">
columnSizingOptions
Omit<ColumnSizingOptions, "onColumnSizingChange" | "onColumnSizingInfoChange">
visibilityOptions
Omit<VisibilityOptions, "onColumnVisibilityChange">
sortingOptions
Omit<SortingOptions<T>, "getSortedRowModel" | "onSortingChange">
groupingOptions
Omit<GroupingOptions, "onGroupingChange">
expandedOptions
Omit<ExpandedOptions<T>, "getExpandedRowModel" | "onExpandedChange">
rowSelectionOptions
Omit<RowSelectionOptions<T>, "onRowSelectionChange">
rowPinningOptions
Omit<RowPinningOptions<T>, "onRowPinningChange">
paginationOptions
Omit<PaginationOptions, "onPaginationChange">
facetedOptions
FacetedOptions<T>
onSelect
(e: Event, row: TableRow<T>): void
onHover
(e: Event, row: TableRow<T> | null): void
onContextmenu
(e: Event, row: TableRow<T>): void | ((e: Event, row: TableRow<T>) => void)[]
state
Partial<TableState>
onStateChange
(updater: Updater<TableState>): void
renderFallbackValue
any
_features
TableFeature<any>[]

An array of extra features that you can add to the table instance.

autoResetAll
boolean

Set this option to override any of the autoReset... feature options.

debugAll
boolean

Set this option to true to output all debugging information to the console.

debugCells
boolean

Set this option to true to output cell debugging information to the console.

debugColumns
boolean

Set this option to true to output column debugging information to the console.

debugHeaders
boolean

Set this option to true to output header debugging information to the console.

debugRows
boolean

Set this option to true to output row debugging information to the console.

debugTable
boolean

Set this option to true to output table debugging information to the console.

defaultColumn
Partial<ColumnDefBase<T, unknown> & StringHeaderIdentifier> | Partial<ColumnDefBase<T, unknown> & IdIdentifier<T, unknown>> | Partial<GroupColumnDefBase<T, unknown> & StringHeaderIdentifier> | Partial<GroupColumnDefBase<T, unknown> & IdIdentifier<T, unknown>> | Partial<AccessorKeyColumnDefBase<T, unknown> & Partial<StringHeaderIdentifier>> | Partial<AccessorKeyColumnDefBase<T, unknown> & Partial<IdIdentifier<T, unknown>>> | Partial<AccessorFnColumnDefBase<T, unknown> & StringHeaderIdentifier> | Partial<AccessorFnColumnDefBase<T, unknown> & IdIdentifier<T, unknown>>

Default column options to use for all column defs supplied to the table.

getRowId
(originalRow: T, index: number, parent?: Row<T> | undefined): string

This optional function is used to derive a unique ID for any given row. If not provided the rows index is used (nested rows join together with . using their grandparents' index eg. index.index.index). If you need to identify individual rows that are originating from any server-side operations, it's suggested you use this function to return an ID that makes sense regardless of network IO/ambiguity eg. a userId, taskId, database ID field, etc.

getSubRows
(originalRow: T, index: number): T[]

This optional function is used to access the sub rows for any given row. If you are using nested rows, you will need to use this function to return the sub rows object (or undefined) from the row.

initialState
InitialTableState

Use this option to optionally pass initial state to the table. This state will be used when resetting various table states either automatically by the table (eg. options.autoResetPageIndex) or via functions like table.resetRowSelection(). Most reset function allow you optionally pass a flag to reset to a blank/default state instead of the initial state.

Table state will not be reset when this object changes, which also means that the initial state object does not need to be stable.

mergeOptions
(defaultOptions: TableOptions<T>, options: Partial<TableOptions<T>>): TableOptions<T>

This option is used to optionally implement the merging of table options.

cellpadding
string | number
cellspacing
string | number
summary
string
width
string | number
globalFilter
string
columnFilters
ColumnFiltersState
columnOrder
ColumnOrderState
columnVisibility
VisibilityState
columnPinning
ColumnPinningState
columnSizing
ColumnSizingState
columnSizingInfo
ColumnSizingInfoState
rowSelection
RowSelectionState
rowPinning
RowPinningState
sorting
SortingState
grouping
GroupingState
expanded
true | Record<string, boolean>
pagination
PaginationState
ui
{ root?: SlotClass; base?: SlotClass; caption?: SlotClass; thead?: SlotClass; tbody?: SlotClass; tfoot?: SlotClass; tr?: SlotClass; th?: SlotClass; td?: SlotClass; separator?: SlotClass; empty?: SlotClass; loading?: SlotClass; }
Diese Komponente unterstützt auch alle nativen <table> HTML-Attribute.

Slots Bearbeiten

Slot Type
expanded
{ row: Row<T>; }
empty
{}
loading
{}
caption
{}
body-top
{}
body-bottom
{}

Expose (Englisch)

Sie können auf die typisierte Komponenteninstanz mit useTemplateRef zugreifen.

<script setup lang="ts">
const table = useTemplateRef('table')
</script>

<template>
  <UTable ref="table" />
</template>

Dies gibt Ihnen Zugang zu den folgenden:

VornameTypen
tableRef nichtRef<HTMLTableElement | null> (englisch)
tableApi nicht vorhandenTable

Theme Bearbeiten

app.config.ts
export default defineAppConfig({
  ui: {
    table: {
      slots: {
        root: 'relative overflow-auto outline-primary/25 focus-visible:outline-3',
        base: 'min-w-full overflow-clip',
        caption: 'sr-only',
        thead: 'relative',
        tbody: 'isolate [&>tr]:data-[selectable=true]:hover:bg-elevated/50 [&>tr]:data-[selectable=true]:outline-primary/25 [&>tr]:data-[selectable=true]:focus-visible:outline-3 [&>tr]:data-[selectable=true]:-outline-offset-3 divide-y divide-default',
        tfoot: 'relative',
        tr: 'data-[selected=true]:bg-elevated/50',
        th: 'px-4 py-3.5 text-sm text-highlighted text-start font-semibold [&:has([role=checkbox])]:pe-0',
        td: 'p-4 text-sm text-muted whitespace-nowrap [&:has([role=checkbox])]:pe-0',
        separator: 'absolute z-1 start-0 w-full h-px bg-(--ui-border-accented)',
        empty: 'py-6 text-center text-sm text-muted',
        loading: 'py-6 text-center'
      },
      variants: {
        pinned: {
          true: {
            th: 'sticky bg-default/75 z-1',
            td: 'sticky bg-default/75 z-1'
          }
        },
        sticky: {
          true: {
            thead: 'sticky top-0 inset-x-0 bg-default/75 backdrop-blur-sm z-1',
            tfoot: 'sticky bottom-0 inset-x-0 bg-default/75 backdrop-blur-sm z-1'
          },
          header: {
            thead: 'sticky top-0 inset-x-0 bg-default/75 backdrop-blur-sm z-1'
          },
          footer: {
            tfoot: 'sticky bottom-0 inset-x-0 bg-default/75 backdrop-blur-sm z-1'
          }
        },
        loading: {
          true: {
            thead: 'after:absolute after:z-1 after:h-px motion-reduce:after:inset-x-0 motion-reduce:after:animate-pulse'
          }
        },
        externalScroll: {
          true: {
            root: 'overflow-visible'
          }
        },
        loadingAnimation: {
          carousel: '',
          'carousel-inverse': '',
          swing: '',
          elastic: ''
        },
        loadingColor: {
          primary: '',
          secondary: '',
          success: '',
          info: '',
          warning: '',
          error: '',
          neutral: ''
        }
      },
      compoundVariants: [
        {
          loading: true,
          loadingColor: 'primary',
          class: {
            thead: 'after:bg-primary'
          }
        },
        {
          loading: true,
          loadingColor: 'neutral',
          class: {
            thead: 'after:bg-inverted'
          }
        },
        {
          loading: true,
          loadingAnimation: 'carousel',
          class: {
            thead: 'motion-safe:after:animate-[carousel_2s_linear_infinite] motion-safe:rtl:after:animate-[carousel-rtl_2s_linear_infinite]'
          }
        },
        {
          loading: true,
          loadingAnimation: 'carousel-inverse',
          class: {
            thead: 'motion-safe:after:animate-[carousel-inverse_2s_linear_infinite] motion-safe:rtl:after:animate-[carousel-inverse-rtl_2s_linear_infinite]'
          }
        },
        {
          loading: true,
          loadingAnimation: 'swing',
          class: {
            thead: 'motion-safe:after:animate-[swing_2s_var(--ease-in-out)_infinite]'
          }
        },
        {
          loading: true,
          loadingAnimation: 'elastic',
          class: {
            thead: 'motion-safe:after:animate-[elastic_2s_var(--ease-in-out)_infinite]'
          }
        }
      ],
      defaultVariants: {
        loadingColor: 'primary',
        loadingAnimation: 'carousel'
      }
    }
  }
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'

export default defineConfig({
  plugins: [
    vue(),
    ui({
      ui: {
        table: {
          slots: {
            root: 'relative overflow-auto outline-primary/25 focus-visible:outline-3',
            base: 'min-w-full overflow-clip',
            caption: 'sr-only',
            thead: 'relative',
            tbody: 'isolate [&>tr]:data-[selectable=true]:hover:bg-elevated/50 [&>tr]:data-[selectable=true]:outline-primary/25 [&>tr]:data-[selectable=true]:focus-visible:outline-3 [&>tr]:data-[selectable=true]:-outline-offset-3 divide-y divide-default',
            tfoot: 'relative',
            tr: 'data-[selected=true]:bg-elevated/50',
            th: 'px-4 py-3.5 text-sm text-highlighted text-start font-semibold [&:has([role=checkbox])]:pe-0',
            td: 'p-4 text-sm text-muted whitespace-nowrap [&:has([role=checkbox])]:pe-0',
            separator: 'absolute z-1 start-0 w-full h-px bg-(--ui-border-accented)',
            empty: 'py-6 text-center text-sm text-muted',
            loading: 'py-6 text-center'
          },
          variants: {
            pinned: {
              true: {
                th: 'sticky bg-default/75 z-1',
                td: 'sticky bg-default/75 z-1'
              }
            },
            sticky: {
              true: {
                thead: 'sticky top-0 inset-x-0 bg-default/75 backdrop-blur-sm z-1',
                tfoot: 'sticky bottom-0 inset-x-0 bg-default/75 backdrop-blur-sm z-1'
              },
              header: {
                thead: 'sticky top-0 inset-x-0 bg-default/75 backdrop-blur-sm z-1'
              },
              footer: {
                tfoot: 'sticky bottom-0 inset-x-0 bg-default/75 backdrop-blur-sm z-1'
              }
            },
            loading: {
              true: {
                thead: 'after:absolute after:z-1 after:h-px motion-reduce:after:inset-x-0 motion-reduce:after:animate-pulse'
              }
            },
            externalScroll: {
              true: {
                root: 'overflow-visible'
              }
            },
            loadingAnimation: {
              carousel: '',
              'carousel-inverse': '',
              swing: '',
              elastic: ''
            },
            loadingColor: {
              primary: '',
              secondary: '',
              success: '',
              info: '',
              warning: '',
              error: '',
              neutral: ''
            }
          },
          compoundVariants: [
            {
              loading: true,
              loadingColor: 'primary',
              class: {
                thead: 'after:bg-primary'
              }
            },
            {
              loading: true,
              loadingColor: 'neutral',
              class: {
                thead: 'after:bg-inverted'
              }
            },
            {
              loading: true,
              loadingAnimation: 'carousel',
              class: {
                thead: 'motion-safe:after:animate-[carousel_2s_linear_infinite] motion-safe:rtl:after:animate-[carousel-rtl_2s_linear_infinite]'
              }
            },
            {
              loading: true,
              loadingAnimation: 'carousel-inverse',
              class: {
                thead: 'motion-safe:after:animate-[carousel-inverse_2s_linear_infinite] motion-safe:rtl:after:animate-[carousel-inverse-rtl_2s_linear_infinite]'
              }
            },
            {
              loading: true,
              loadingAnimation: 'swing',
              class: {
                thead: 'motion-safe:after:animate-[swing_2s_var(--ease-in-out)_infinite]'
              }
            },
            {
              loading: true,
              loadingAnimation: 'elastic',
              class: {
                thead: 'motion-safe:after:animate-[elastic_2s_var(--ease-in-out)_infinite]'
              }
            }
          ],
          defaultVariants: {
            loadingColor: 'primary',
            loadingAnimation: 'carousel'
          }
        }
      }
    })
  ]
})
Some colors in compoundVariants are omitted for readability. Check out the source code on GitHub.

Changelog (englisch)

No recent changes