Table

A powerful, responsive table and datagrids built using Tanstack

Examples

Basic

PropDefaultTypeDescription
columns[]ArrayTable columns.
data[]ArrayTable data.
Preview
Code
Data
First NameLast NameAgeVisitsStatusProfile Progress
LaurettaFranecki36645relationship81
BerthaSmitham6533relationship60
MargaretJast20329single33
AlexandreSanford35227relationship72
DamonO'Connell16840single78

Row Selection

Row selection allows you to select rows in the table. This is useful when you want to select rows in the table.

PropDefaultTypeDescription
modelValue[]ArraySelected rows.
enableRowSelectionfalseBooleanEnable row selection.
enableMultiRowSelectionfalseBooleanEnable multiple row selection.
rowIdidStringRow id to uniquely identify each row.
enableSubRowSelectionfalseBooleanEnable sub row selection.
@select-EventEmitted when a row is selected.
@select-all-EventEmitted when all rows are selected.
Preview
Code
Data
First NameLast NameAgeVisitsStatusProfile Progress
JedHansen23211single67
JasminLebsack26834single61
ZachariahBarrows29401single56
KennethMitchell20373relationship10
BartonKiehn26189relationship5
DelfinaWilkinson36599relationship93
MarlonBahringer21332relationship38
AshtynEmard28891relationship0
MyrlSchoen0224single40
HellenMoen-Goldner22520complicated50
of row(s) selected.

Loading

Loading allows you to show a loading progress indicator in the table. This is useful when you want to show a loading progress indicator in the table.

PropDefaultTypeDescription
loadingfalseBooleanLoading state.
Preview
Code
Data
First NameLast NameAgeVisitsStatusProfile Progress
ColtenMoore24147relationship24
DinaRowe34162relationship23
OrionKoepp-Gleason0814single94
CloydRunolfsdottir25538complicated46
ShayleeBreitenberg19341complicated92
TrentEbert38866single57
CamrenMonahan247relationship52
MyraBreitenberg23666complicated22
LoraineHuels27191complicated90
LomaCole9323single65

Pagination

Pagination allows you to paginate rows in the table. This is useful when you want to paginate rows in the table.

PropDefaultTypeDescription
pagination{pageIndex: 0, pageSize: 10}{pageIndex: Number, pageSize: Number}Pagination default configuration.
manualPaginationfalseBooleanEnable manual pagination.
Preview
Code
Data
First NameLast NameAgeVisitsStatusProfile Progress
CaylaMayert36778single76
KodyGerlach2839single77
DiegoGerhold2118relationship91
AdolfoRuecker15871single99
EllenDurgan24293single30
Page 1 of

Sorting

Sorting allows you to sort columns in ascending or descending order. This is useful when you want to sort columns in the table.

PropDefaultTypeDescription
enableSorting-booleanEnable all column sorting
column.enableSorting-booleanEnable specific column sorting
enableMultiColumnSort-booleanEnable multi-column sorting
Preview
Code
Data
Status
AimeeGoyette9328relationship63
RashadWatsica31986single82
KellieGreen40869single39
MikeProhaska37689complicated21
BertramWisoky2190single81
MafaldaRussel05complicated97
SandrineMraz2157single64
ElvieMills38290single40
MikaylaMills13427complicated89
EileenMetz19840single6

Visibility

Visibility allows you to show or hide columns in the table. This is useful when you want to show or hide columns in the table.

PropDefaultTypeDescription
columnVisibility-objectThe column visibility state
Preview
Code
Data
First NameLast NameAgeVisitsStatusProfile Progress
RheaSanford1465relationship71
OzellaSkiles37989relationship35
ArielleHowell30481single45
RicoLubowitz3298relationship40
KeithMcCullough3507complicated6

Global Filtering

Global filtering allows you to filter rows based on the value entered in the filter input. This is useful when you want to filter rows in the table.

PropDefaultTypeDescription
globalFilter-stringThe global filter value
Preview
Code
Data
First NameLast NameAgeVisitsStatusProfile Progress
CobyTurner6515relationship36
PearlWolff12919complicated30
BruceCrist0324single4
AlysonReichel15755relationship71
DomenickOberbrunner30513complicated23
VirginieProsacco31623single84
FrancescoLarkin14483single74
DamarisWillms-Parisian3649relationship79
VicentaLuettgen18464complicated59
HipolitoStark16679complicated68

Column Filtering

Column filtering allows you to filter columns based on the value entered in the filter input. This is useful when you want to filter columns in the table.

PropDefaultTypeDescription
enableColumnFilter-booleanEnable all column filtering
column.enableColumnFilter-booleanEnable specific column filtering
Preview
Code
Data
First NameLast NameAgeVisitsStatusProfile Progress
IdellConsidine-McLaughlin1182complicated96
SabrynaLarson30517single28
BerylFunk39861complicated99
JosieWyman14869single32
ArielSchimmel1986complicated92

Column Ordering

Column ordering allows you to reorder columns by dragging and dropping them. This is useful when you want to change the order of columns in the table.

Preview
Code
Data
First NameLast NameAgeVisitsStatusProfile Progress
LueBraun3778relationship20
BlancheLynch-Jakubowski2837complicated46
DaynaHarris5552complicated16
GraceLakin38445complicated38
AdelbertPfeffer3756complicated85

Column Pinning

Column pinning allows you to pin columns to the left or right of the table. This is useful when you have a large number of columns and you want to keep some columns in view while scrolling.

PropDefaultTypeDescription
columnPins-{ left: Array, right: Array }Defines which columns are pinned to the left or right.
Preview
Code
Data
StatusFirst NameLast NameAgeVisitsProfile Progress
complicatedJulietFrami937885
singleFilomenaKuhn1652086
singleGeneO'Conner1585012
complicatedErinMarquardt1949016
relationshipLiaDickinson771564

Expanding

Expanding allows you to expand rows to show additional information. This is useful when you want to show additional information about a row.

Preview
Code
Data
First NameLast NameAgeVisitsStatusProfile Progress
RodolfoLesch31142relationship89
ChrisD'Amore18125relationship99
PercivalSchaden7223relationship11
AlfordDaniel26940single4
EunaNicolas15826single66

Grouping

Grouping allows you to group rows based on a column value. This is useful when you want to group rows in the table.

Preview
Code
Data
NameInfo
First NameLast NameAgeVisitsStatusProfile Progress
WillaDurgan10667single95
FelicityKunde6542complicated75
OrionGerlach2401relationship49
AmaliaArmstrong26368relationship98
AlyceCorwin31656complicated87

Server-side

Allows you to fetch data from the server. This is useful when you want to fetch data from the server.

Preview
Code
Data
NameUrl
bulbasaurhttps://pokeapi.co/api/v2/pokemon/1/
ivysaurhttps://pokeapi.co/api/v2/pokemon/2/
venusaurhttps://pokeapi.co/api/v2/pokemon/3/
charmanderhttps://pokeapi.co/api/v2/pokemon/4/
charmeleonhttps://pokeapi.co/api/v2/pokemon/5/
Page 1 of 261

Customization

Configure the progress using the una prop and utility classes.

PropDefaultTypeDescription
columns.meta.una{}ObjectColumn Una meta data.
una{}ObjectGlobal Una attribute.
Preview
Code
Data
First NameLast NameAgeVisitsStatusProfile Progress
CaliNikolaus0205complicated92
JacquelynLakin24933single91
ChynaBechtelar13938single50
LeonieRatke2296complicated73
JoshuahGusikowski16215complicated46

Slots

NamePropsDescription
{column}-filtercolumnColumn filter slot.
{column}-headercolumnColumn header slot.
{column}-cellcellColumn cell slot.
{column}-footercolumnColumn footer slot.
headertableHeader slot.
bodytableBody slot.
rawrowRow slot.
footertableFooter slot.
expandedrowExpanded slot.
empty-Empty slot.
loading-Loading slot.
Preview
Code
Data
Account
BM
Bell Murazik
Abagail15@yahoo.com
BellMuraziksingle
11%
PP
Palma Prohaska
Ford73@yahoo.com
PalmaProhaskacomplicated
58%
ML
Marquis Langosh
Kattie.Rogahn88@yahoo.com
MarquisLangoshrelationship
39%
QL
Quinton Langosh
Hailey.Cummings55@gmail.com
QuintonLangoshcomplicated
34%
GL
Guillermo Lang
Ignatius_Nolan53@hotmail.com
GuillermoLangrelationship
50%
AV
Abdullah Von
Adela.Stokes@yahoo.com
AbdullahVonsingle
71%
AK
Albina Keeling
Grayson57@yahoo.com
AlbinaKeelingcomplicated
80%
AW
Amber Willms
Samanta_Kihn@gmail.com
AmberWillmsrelationship
27%
MR
Mateo Ratke
Carolanne39@gmail.com
MateoRatkecomplicated
84%
NK
Noemie Koelpin
Rosanna.Kuhlman@yahoo.com
NoemieKoelpinsingle
0%
Page 1 of

Presets

shortcuts/table.ts
type TablePrefix = 'table'

export const staticTable: Record<`${TablePrefix}-${string}` | TablePrefix, string> = {
  // config
  'table-default-variant': 'table-solid-gray',
  'table': '',

  // table-root
  'table-root-wrapper': 'relative w-full overflow-x-auto overflow-y-hidden border border-base rounded-md',
  'table-root': 'w-full caption-bottom text-sm',
  'table-body': '[&_tr:last-child]:border-0',
  'table-caption': 'mt-4 text-sm text-muted',

  // table-head
  'table-head': 'h-12 px-4 text-left align-middle font-medium text-muted [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-0.5',
  'table-head-pinned': 'sticky bg-base',
  'table-head-pinned-left': 'left-0',
  'table-head-pinned-right': 'right-0',

  // table-header
  'table-header': '[&_tr]:border-b [&_tr]:border-base',

  // table-row
  'table-row': 'border-b border-base transition-colors hover:bg-muted data-[filter=true]:hover:bg-base data-[state=selected]:bg-muted',

  // table-cell
  'table-cell': 'p-4 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-0.5',
  'table-cell-pinned': 'sticky bg-base',
  'table-cell-pinned-left': 'left-0',
  'table-cell-pinned-right': 'right-0',

  // table-empty
  'table-empty-row': '',
  'table-empty-cell': 'p-4 whitespace-nowrap align-middle text-sm text-muted bg-base',
  'table-empty': 'flex items-center justify-center py-10',

  // table-loading
  'table-loading-icon': 'animate-spin text-lg', // TODO: to add
  'table-loading-icon-name': 'i-lucide-refresh-ccw', // TODO: to add
  'table-loading-row': 'data-[loading=true]:border-0 absolute inset-x-0 -mt-1.5px',
  'table-loading-cell': '',
  'table-loading': 'absolute inset-x-0 overflow-hidden p-0',

  // table-footer
  'table-footer': 'border-t border-base bg-muted font-medium [&>tr]:last:border-b-0',
}

export const dynamicTable: [RegExp, (params: RegExpExecArray) => string][] = [
]

export const table = [
  ...dynamicTable,
  staticTable,
]

Props

types/table.ts
import type {
  ColumnDef,
  GroupColumnDef,
} from '@tanstack/vue-table'
import type { HTMLAttributes } from 'vue'
import type { NProgressProps } from './progress'

export interface NTableProps<TData, TValue> extends NTableRootProps {
  /**
   * @see https://tanstack.com/table/latest/docs/guide/data
   */
  data: TData[]
  /**
   * @see https://tanstack.com/table/latest/docs/api/core/column
   */
  columns: ColumnDef<TData, TValue>[] | GroupColumnDef<TData, TValue>[]
  /**
   * @see https://tanstack.com/table/latest/docs/api/core/table#getrowid
   */
  rowId?: string
  /**
   * @see https://tanstack.com/table/latest/docs/api/core/table#autoresetall
   */
  autoResetAll?: boolean
  /**
   * @see https://tanstack.com/table/latest/docs/api/features/row-selection#enablerowselection
   */
  enableRowSelection?: boolean
  /**
   * @see https://tanstack.com/table/latest/docs/api/features/row-selection#enablemultirowselection
   */
  enableMultiRowSelection?: boolean
  /**
   * @see https://tanstack.com/table/latest/docs/api/features/row-selection#enablesubrowselection
   */
  enableSubRowSelection?: boolean
  /**
   * @see https://tanstack.com/table/latest/docs/api/features/column-filtering#enablecolumnfilters
   */
  enableColumnFilters?: boolean
  /**
   * @see https://tanstack.com/table/latest/docs/api/features/sorting#enablesorting
   */
  enableSorting?: boolean
  /**
   * @see https://tanstack.com/table/latest/docs/api/features/sorting#enablemultisort
   */
  enableMultiSort?: boolean
  /**
   * @see https://tanstack.com/table/latest/docs/api/features/sorting#enablemultiremove
   */
  enableMultiRemove?: boolean
  /**
   * @see https://tanstack.com/table/latest/docs/api/features/sorting#enablesortingremoval
   */
  enableSortingRemoval?: boolean
  /**
   * @see https://tanstack.com/table/latest/docs/api/features/sorting#manualsorting
   */
  manualSorting?: boolean
  /**
   * @see https://tanstack.com/table/latest/docs/api/features/sorting#maxmultisortcolcount
   */
  maxMultiSortColCount?: number
  /**
   * @see https://tanstack.com/table/latest/docs/api/features/pagination#manualpagination
   */
  manualPagination?: boolean
  /**
   * @see https://tanstack.com/table/latest/docs/api/features/pagination#pagecount
   */
  pageCount?: number
  /**
   * @see https://tanstack.com/table/latest/docs/api/features/pagination#rowcount
   */
  rowCount?: number
  /**
   * @see https://tanstack.com/table/latest/docs/api/features/pagination#autoresetpageindex
   */
  autoResetPageIndex?: boolean
  /**
   * @see https://tanstack.com/table/latest/docs/api/features/sorting#sortingfns
   */
  sortingFns?: Record<string, (a: any, b: any) => number>
  /**
   * @see https://tanstack.com/table/latest/docs/api/features/sorting#sortdescfirst-1
   */
  sortDescFirst?: boolean
  /**
   * @see https://tanstack.com/table/latest/docs/api/features/sorting#ismultisortevent
   */
  isMultiSortEvent?: (e: unknown) => boolean

  // sub-components props
  _tableHead?: NTableHeadProps
  _tableHeader?: NTableHeaderProps
  _tableFooter?: NTableFooterProps
  _tableBody?: NTableBodyProps
  _tableCaption?: NTableCaptionProps
  _tableRow?: NTableRowProps
  _tableCell?: NTableCellProps
  _tableEmpty?: NTableEmptyProps
  _tableLoading?: NTableLoadingProps

  loading?: boolean

  /**
   * `UnaUI` preset configuration
   *
   * @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/table.ts
   */
  una?: NTableUnaProps
}

export interface NTableRootProps {
  class?: HTMLAttributes['class']

  una?: Pick<NTableUnaProps, 'tableRoot' | 'tableRootWrapper'>
}

export interface NTableBodyProps {
  class?: HTMLAttributes['class']

  una?: Pick<NTableUnaProps, 'tableBody'>
}

export interface NTableHeadProps {
  class?: HTMLAttributes['class']

  dataPinned?: 'left' | 'right' | false

  una?: Pick<NTableUnaProps, 'tableHead'>
}

export interface NTableHeaderProps {
  class?: HTMLAttributes['class']

  una?: Pick<NTableUnaProps, 'tableHeader'>
}

export interface NTableFooterProps {
  class?: HTMLAttributes['class']

  una?: Pick<NTableUnaProps, 'tableFooter'>
}

export interface NTableRowProps {
  class?: HTMLAttributes['class']

  una?: Pick<NTableUnaProps, 'tableRow'>
}

export interface NTableCellProps {
  class?: HTMLAttributes['class']

  dataPinned?: 'left' | 'right' | false

  una?: Pick<NTableUnaProps, 'tableCell'>
}

export interface NTableEmptyProps {
  class?: HTMLAttributes['class']
  colspan?: number

  _tableCell?: NTableCellProps
  _tableRow?: NTableRowProps

  una?: Pick<NTableUnaProps, 'tableEmpty' | 'tableRow' | 'tableCell'>
}

export interface NTableLoadingProps {
  size?: HTMLAttributes['class']
  enabled?: boolean
  class?: HTMLAttributes['class']
  colspan?: number

  _tableCell?: NTableCellProps
  _tableRow?: NTableRowProps
  _tableProgress?: NProgressProps

  una?: Pick<NTableUnaProps, 'tableLoading' | 'tableRow' | 'tableCell'>
}

export interface NTableCaptionProps {
  class?: HTMLAttributes['class']

  una?: Pick<NTableUnaProps, 'tableCaption'>
}

interface NTableUnaProps {
  tableRoot?: HTMLAttributes['class']
  tableRootWrapper?: HTMLAttributes['class']
  tableBody?: HTMLAttributes['class']
  tableHead?: HTMLAttributes['class']
  tableHeader?: HTMLAttributes['class']
  tableFooter?: HTMLAttributes['class']
  tableRow?: HTMLAttributes['class']
  tableCell?: HTMLAttributes['class']
  tableCaption?: HTMLAttributes['class']
  tableEmpty?: HTMLAttributes['class']
  tableLoading?: HTMLAttributes['class']
}

Components

Table.vue
TableRoot.vue
TableHeader.vue
TableHead.vue
TableBody.vue
TableFooter.vue
TableCell.vue
TableRow.vue
TableEmpty.vue
TableLoading.vue
TableCaption.vue
<script setup lang="ts" generic="TData, TValue">
import type {
  ColumnFiltersState,
  ColumnOrderState,
  ColumnPinningState,
  ExpandedState,
  GroupingState,
  Header,
  PaginationState,
  SortingState,
  VisibilityState,
} from '@tanstack/vue-table'
import type { NTableProps } from '../../../types'

import {
  FlexRender,
  getCoreRowModel,
  getExpandedRowModel,
  getFilteredRowModel,
  getPaginationRowModel,
  getSortedRowModel,
  useVueTable,
} from '@tanstack/vue-table'

import { computed, h } from 'vue'

import { cn, valueUpdater } from '../../../utils'
import Button from '../../elements/Button.vue'
import Checkbox from '../../forms/Checkbox.vue'
import Input from '../../forms/Input.vue'
import TableBody from './TableBody.vue'
import TableCell from './TableCell.vue'
import TableEmpty from './TableEmpty.vue'
import TableFooter from './TableFooter.vue'
import TableHead from './TableHead.vue'
import TableHeader from './TableHeader.vue'
import TableLoading from './TableLoading.vue'
import TableRoot from './TableRoot.vue'
import TableRow from './TableRow.vue'

const props = withDefaults(defineProps <NTableProps<TData, TValue>>(), {
  enableMultiRowSelection: true,
})

const emit = defineEmits(['select', 'selectAll', 'expand'])

const slots = defineSlots()

const rowSelection = defineModel<Record<string, boolean>>('modelValue')
const sorting = defineModel<SortingState>('sorting')
const columnVisibility = defineModel<VisibilityState>('columnVisibility')
const columnFilters = defineModel<ColumnFiltersState>('columnFilters')
const globalFilter = defineModel<string>('globalFilter')
const columnOrder = defineModel<ColumnOrderState>('columnOrder')
const columnPinning = defineModel<ColumnPinningState>('columnPinning')
const expanded = defineModel<ExpandedState>('expanded')
const grouping = defineModel<GroupingState>('grouping')
const pagination = defineModel<PaginationState>('pagination', {
  default: () => ({
    pageIndex: 0,
    pageSize: 10,
  }),
})

const columnsWithMisc = computed(() => {
  let data = []

  // add selection column
  data = props.enableRowSelection
    ? [
        {
          accessorKey: 'selection',
          header: props.enableMultiRowSelection
            ? ({ table }: any) => h(Checkbox, {
                'checked': table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && 'indeterminate'),
                'onUpdate:checked': (value: boolean) => {
                  table.toggleAllPageRowsSelected(!!value)
                  emit('selectAll', table.getRowModel().rows)
                },
                'areaLabel': 'Select all rows',
              })
            : '',
          cell: ({ row }: any) => h(Checkbox, {
            'checked': row.getIsSelected() ?? false,
            'onUpdate:checked': (value: boolean) => {
              row.toggleSelected(!!value)
              emit('select', row)
            },
            'areaLabel': 'Select row',
          }),
          enableSorting: false,
          enableHiding: false,
        },
        ...props.columns,
      ]
    : props.columns

  // add expanded column
  data = slots.expanded
    ? [
        {
          accessorKey: 'expanded',
          header: '',
          cell: ({ row }: any) => h(Button, {
            size: 'xs',
            icon: true,
            square: true,
            btn: 'ghost-gray',
            label: 'i-radix-icons-chevron-down',
            onClick: () => {
              row.toggleExpanded()
              emit('expand', row)
            },
            una: {
              btnIconLabel: cn(
                'transform transition-transform duration-200',
                row.getIsExpanded() ? '-rotate-180' : 'rotate-0',
              ),
            },
          }),
          enableSorting: false,
          enableHiding: false,
        },
        ...data,
      ]
    : data

  return data
})

const table = useVueTable({
  get data() {
    return props.data ?? []
  },
  get columns() {
    return columnsWithMisc.value ?? []
  },
  state: {
    get sorting() { return sorting.value },
    get columnFilters() { return columnFilters.value },
    get globalFilter() { return globalFilter.value },
    get rowSelection() { return rowSelection.value },
    get columnVisibility() { return columnVisibility.value },
    get pagination() { return pagination.value },
    get columnOrder() { return columnOrder.value },
    get columnPinning() { return columnPinning.value },
    get expanded() { return expanded.value },
    get grouping() { return grouping.value },
  },

  enableMultiRowSelection: props.enableMultiRowSelection,
  enableSubRowSelection: props.enableSubRowSelection,
  autoResetAll: props.autoResetAll,
  enableRowSelection: props.enableRowSelection,
  enableColumnFilters: props.enableColumnFilters,
  manualPagination: props.manualPagination,
  manualSorting: props.manualSorting,
  pageCount: props.pageCount,
  rowCount: props.rowCount,
  autoResetPageIndex: props.autoResetPageIndex,
  enableSorting: props.enableSorting,
  enableSortingRemoval: props.enableSortingRemoval,
  enableMultiSort: props.enableMultiSort,
  enableMultiRemove: props.enableMultiRemove,
  maxMultiSortColCount: props.maxMultiSortColCount,
  sortingFns: props.sortingFns,
  isMultiSortEvent: props.isMultiSortEvent,

  getCoreRowModel: getCoreRowModel(),
  getSortedRowModel: getSortedRowModel(),
  getFilteredRowModel: getFilteredRowModel(),
  getPaginationRowModel: getPaginationRowModel(),
  getRowId: (row: any) => props.rowId ? row[props.rowId] : row.id,
  getExpandedRowModel: getExpandedRowModel(),

  onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sorting),
  onRowSelectionChange: updaterOrValue => valueUpdater(updaterOrValue, rowSelection),
  onColumnVisibilityChange: updaterOrValue => valueUpdater(updaterOrValue, columnVisibility),
  onColumnFiltersChange: updaterOrValue => valueUpdater(updaterOrValue, columnFilters),
  onGlobalFilterChange: updaterOrValue => valueUpdater(updaterOrValue, globalFilter),
  onPaginationChange: updaterOrValue => valueUpdater(updaterOrValue, pagination),
  onColumnOrderChange: updaterOrValue => valueUpdater(updaterOrValue, columnOrder),
  onColumnPinningChange: updaterOrValue => valueUpdater(updaterOrValue, columnPinning),
  onExpandedChange: updaterOrValue => valueUpdater(updaterOrValue, expanded),
  onGroupingChange: updaterOrValue => valueUpdater(updaterOrValue, grouping),
})

function getHeaderColumnFiltersCount(headers: Header<unknown, unknown>[]): number {
  let count = 0
  headers.forEach((header) => {
    if (header.column.columnDef.enableColumnFilter)
      count++
  })

  return count
}

defineExpose({
  ...table,
})
</script>

<template>
  <TableRoot
    :class="props.class"
    :una
  >
    <!-- header -->
    <TableHeader
      :una
      v-bind="props._tableHeader"
    >
      <slot name="header" :table="table">
        <TableRow
          v-for="headerGroup in table.getHeaderGroups()"
          :key="headerGroup.id"
          :una
          v-bind="props._tableRow"
        >
          <!-- headers -->
          <TableHead
            v-for="header in headerGroup.headers"
            :key="header.id"
            :colspan="header.colSpan"
            :data-pinned="header.column.getIsPinned()"
            :una
            v-bind="{ ...props._tableHead, ...header.column.columnDef.meta }"
          >
            <Button
              v-if="header.column.columnDef.enableSorting || (header.column.columnDef.enableSorting !== false && enableSorting)"
              btn="ghost-gray"
              size="sm"
              class="font-normal -ml-1em"
              :una="{
                btnTrailing: 'text-sm',
              }"
              :trailing="header.column.getIsSorted() === 'asc'
                ? 'i-lucide-arrow-up-wide-narrow' : header.column.getIsSorted() === 'desc'
                  ? 'i-lucide-arrow-down-narrow-wide' : 'i-lucide-arrow-up-down'"
              @click="header.column.toggleSorting(
                header.column.getIsSorted() === 'asc' ? undefined : header.column.getIsSorted() !== 'desc',
                enableMultiSort,
              )"
            >
              <slot
                :name="`${header.id}-header`"
                :column="header.column"
              >
                <FlexRender
                  v-if="!header.isPlaceholder"
                  :render="header.column.columnDef.header"
                  :props="header.getContext()"
                />
              </slot>
            </Button>

            <slot
              v-else
              :name="`${header.id}-header`"
              :column="header.column"
            >
              <FlexRender
                v-if="!header.isPlaceholder"
                :render="header.column.columnDef.header"
                :props="header.getContext()"
              />
            </slot>
          </TableHead>
        </TableRow>

        <!-- column filters -->
        <template
          v-for="headerGroup in table.getHeaderGroups()"
          :key="headerGroup.id"
        >
          <TableRow
            v-if="getHeaderColumnFiltersCount(headerGroup.headers) > 0 || enableColumnFilters"
            data-filter="true"
            :una
            v-bind="props._tableRow"
          >
            <TableHead
              v-for="header in headerGroup.headers"
              :key="header.id"
              :una
              :colspan="header.colSpan"
              :data-pinned="header.column.getIsPinned()"
              v-bind="{ ...props._tableHead, ...header.column.columnDef.meta }"
            >
              <slot
                v-if="header.id !== 'selection' && ((header.column.columnDef.enableColumnFilter !== false && enableColumnFilters) || header.column.columnDef.enableColumnFilter)"
                :name="`${header.id}-filter`"
                :column="header.column"
              >
                <Input
                  class="w-auto text-sm text-base"
                  :model-value="header.column.getFilterValue() as string"
                  :placeholder="header.column.columnDef.header"
                  @update:model-value="header.column.setFilterValue($event)"
                />
              </slot>
            </TableHead>
          </TableRow>
        </template>
      </slot>

      <TableLoading
        :enabled="props.loading"
        :una
        v-bind="props._tableLoading"
      >
        <slot name="loading" />
      </TableLoading>
    </TableHeader>

    <!-- body -->
    <TableBody
      :una
      v-bind="props._tableBody"
    >
      <slot name="body" :table="table">
        <template v-if="table.getRowModel().rows?.length">
          <template
            v-for="row in table.getRowModel().rows"
            :key="row.id"
          >
            <TableRow
              :data-state="row.getIsSelected() && 'selected'"
              :una
              v-bind="props._tableRow"
            >
              <slot
                name="row"
                :row="row"
              >
                <!-- rows -->
                <TableCell
                  v-for="cell in row.getVisibleCells()"
                  :key="cell.id"
                  :data-pinned="cell.column.getIsPinned()"
                  :una
                  v-bind="{ ...props._tableCell, ...cell.column.columnDef.meta }"
                >
                  <slot
                    :name="`${cell.column.id}-cell`"
                    :cell="cell"
                  >
                    <FlexRender
                      :render="cell.column.columnDef.cell"
                      :props="cell.getContext()"
                    />
                  </slot>
                </TableCell>
              </slot>
            </TableRow>

            <!-- expanded -->
            <TableRow
              v-if="row.getIsExpanded() && $slots.expanded"
              :una
              v-bind="props._tableRow"
            >
              <TableCell
                :colspan="row.getAllCells().length"
                :una
                v-bind="props._tableCell"
              >
                <slot name="expanded" :row="row" />
              </TableCell>
            </TableRow>
          </template>
        </template>

        <TableEmpty
          v-else
          :colspan="table.getAllLeafColumns().length"
          :una
          v-bind="props._tableEmpty"
        >
          <slot name="empty" />
        </TableEmpty>
      </slot>
    </TableBody>

    <!-- footer -->
    <TableFooter
      v-if="table.getFooterGroups().length > 0"
      :una
      v-bind="props._tableFooter"
    >
      <slot name="footer" :table="table">
        <template
          v-for="footerGroup in table.getFooterGroups()"
          :key="footerGroup.id"
        >
          <TableRow
            v-if="footerGroup.headers.length > 0"
            :una
            v-bind="props._tableRow"
          >
            <template
              v-for="header in footerGroup.headers"
              :key="header.id"
            >
              <TableHead
                v-if="header.column.columnDef.footer"
                :colspan="header.colSpan"
                :una
                v-bind="{ ...props._tableHead, ...header.column.columnDef.meta }"
              >
                <slot :name="`${header.id}-footer`" :column="header.column">
                  <FlexRender
                    v-if="!header.isPlaceholder"
                    :render="header.column.columnDef.footer"
                    :props="header.getContext()"
                  />
                </slot>
              </TableHead>
            </template>
          </TableRow>
        </template>
      </slot>
    </TableFooter>
  </TableRoot>
</template>