diff --git a/packages/ui/package.json b/packages/ui/package.json
index f7ede7ae..1a7cc6c3 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -20,6 +20,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.1.18",
+ "@tanstack/vue-table": "^8.21.3",
"@vee-validate/zod": "^4.15.1",
"@vueuse/core": "^14.1.0",
"class-variance-authority": "^0.7.1",
diff --git a/packages/ui/src/components/table/Table.vue b/packages/ui/src/components/table/Table.vue
new file mode 100644
index 00000000..e4a80d46
--- /dev/null
+++ b/packages/ui/src/components/table/Table.vue
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/packages/ui/src/components/table/TableBody.vue b/packages/ui/src/components/table/TableBody.vue
new file mode 100644
index 00000000..42e017d5
--- /dev/null
+++ b/packages/ui/src/components/table/TableBody.vue
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
diff --git a/packages/ui/src/components/table/TableCaption.vue b/packages/ui/src/components/table/TableCaption.vue
new file mode 100644
index 00000000..233376ea
--- /dev/null
+++ b/packages/ui/src/components/table/TableCaption.vue
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
diff --git a/packages/ui/src/components/table/TableCell.vue b/packages/ui/src/components/table/TableCell.vue
new file mode 100644
index 00000000..aa750d4f
--- /dev/null
+++ b/packages/ui/src/components/table/TableCell.vue
@@ -0,0 +1,22 @@
+
+
+
+ |
+
+ |
+
diff --git a/packages/ui/src/components/table/TableEmpty.vue b/packages/ui/src/components/table/TableEmpty.vue
new file mode 100644
index 00000000..7eeecf68
--- /dev/null
+++ b/packages/ui/src/components/table/TableEmpty.vue
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/ui/src/components/table/TableFooter.vue b/packages/ui/src/components/table/TableFooter.vue
new file mode 100644
index 00000000..c8900d8a
--- /dev/null
+++ b/packages/ui/src/components/table/TableFooter.vue
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
diff --git a/packages/ui/src/components/table/TableHead.vue b/packages/ui/src/components/table/TableHead.vue
new file mode 100644
index 00000000..23726136
--- /dev/null
+++ b/packages/ui/src/components/table/TableHead.vue
@@ -0,0 +1,17 @@
+
+
+
+
+
+ |
+
diff --git a/packages/ui/src/components/table/TableHeader.vue b/packages/ui/src/components/table/TableHeader.vue
new file mode 100644
index 00000000..70c290a4
--- /dev/null
+++ b/packages/ui/src/components/table/TableHeader.vue
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
diff --git a/packages/ui/src/components/table/TableRow.vue b/packages/ui/src/components/table/TableRow.vue
new file mode 100644
index 00000000..c723bea3
--- /dev/null
+++ b/packages/ui/src/components/table/TableRow.vue
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
diff --git a/packages/ui/src/components/table/index.ts b/packages/ui/src/components/table/index.ts
new file mode 100644
index 00000000..3be308b5
--- /dev/null
+++ b/packages/ui/src/components/table/index.ts
@@ -0,0 +1,9 @@
+export { default as Table } from "./Table.vue"
+export { default as TableBody } from "./TableBody.vue"
+export { default as TableCaption } from "./TableCaption.vue"
+export { default as TableCell } from "./TableCell.vue"
+export { default as TableEmpty } from "./TableEmpty.vue"
+export { default as TableFooter } from "./TableFooter.vue"
+export { default as TableHead } from "./TableHead.vue"
+export { default as TableHeader } from "./TableHeader.vue"
+export { default as TableRow } from "./TableRow.vue"
diff --git a/packages/ui/src/components/table/utils.ts b/packages/ui/src/components/table/utils.ts
new file mode 100644
index 00000000..3d4fd12f
--- /dev/null
+++ b/packages/ui/src/components/table/utils.ts
@@ -0,0 +1,10 @@
+import type { Updater } from "@tanstack/vue-table"
+
+import type { Ref } from "vue"
+import { isFunction } from "@tanstack/vue-table"
+
+export function valueUpdater(updaterOrValue: Updater, ref: Ref) {
+ ref.value = isFunction(updaterOrValue)
+ ? updaterOrValue(ref.value)
+ : updaterOrValue
+}
diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts
index 1a1015de..a0e2cdd7 100644
--- a/packages/ui/src/index.ts
+++ b/packages/ui/src/index.ts
@@ -27,6 +27,7 @@ export * from './components/slider/index'
export * from './components/sonner/index'
export * from './components/spinner/index'
export * from './components/switch/index'
+export * from './components/table/index'
export * from './components/tabs/index'
export * from './components/textarea/index'
export * from './components/tooltip/index'
\ No newline at end of file
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 12a6dea0..9f1261d4 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -143,6 +143,9 @@ importers:
'@tailwindcss/vite':
specifier: ^4.1.18
version: 4.1.18(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))
+ '@tanstack/vue-table':
+ specifier: ^8.21.3
+ version: 8.21.3(vue@3.5.26(typescript@5.9.3))
'@vee-validate/zod':
specifier: ^4.15.1
version: 4.15.1(vue@3.5.26(typescript@5.9.3))(zod@3.25.76)
@@ -1790,9 +1793,19 @@ packages:
peerDependencies:
vite: ^5.2.0 || ^6 || ^7
+ '@tanstack/table-core@8.21.3':
+ resolution: {integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==}
+ engines: {node: '>=12'}
+
'@tanstack/virtual-core@3.13.17':
resolution: {integrity: sha512-m5mRfGNcL5GUzluWNom0Rmg8P8Dg3h6PnJtJBmJcBiJvkV+vufmUfLnVzKSPGQtmvzMW/ZuUdvL+SyjIUvHV3A==}
+ '@tanstack/vue-table@8.21.3':
+ resolution: {integrity: sha512-rusRyd77c5tDPloPskctMyPLFEQUeBzxdQ+2Eow4F7gDPlPOB1UnnhzfpdvqZ8ZyX2rRNGmqNnQWm87OI2OQPw==}
+ engines: {node: '>=12'}
+ peerDependencies:
+ vue: '>=3.2'
+
'@tanstack/vue-virtual@3.13.17':
resolution: {integrity: sha512-w+Btl94IkuL7c2hSVSD0t8tXfhLRnKppOlGKlzBGjw0SrlIgKbiOJv/FcSTCO3SeyI9h0sx2gF/cO/PONtkidw==}
peerDependencies:
@@ -3692,13 +3705,14 @@ packages:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'}
- type-is@2.0.1:
- resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==}
- engines: {node: '>= 0.6'}
type-fest@4.41.0:
resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==}
engines: {node: '>=16'}
+ type-is@2.0.1:
+ resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==}
+ engines: {node: '>= 0.6'}
+
typescript-eslint@8.52.0:
resolution: {integrity: sha512-atlQQJ2YkO4pfTVQmQ+wvYQwexPDOIgo+RaVcD7gHgzy/IQA+XTyuxNM9M9TVXvttkF7koBHmcwisKdOAf2EcA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -3805,6 +3819,7 @@ packages:
vary@1.1.2:
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
engines: {node: '>= 0.8'}
+
vee-validate@4.15.1:
resolution: {integrity: sha512-DkFsiTwEKau8VIxyZBGdO6tOudD+QoUBPuHj3e6QFqmbfCRj1ArmYWue9lEp6jLSWBIw4XPlDLjFIZNLdRAMSg==}
peerDependencies:
@@ -5394,8 +5409,15 @@ snapshots:
tailwindcss: 4.1.18
vite: 7.3.0(@types/node@24.10.4)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)
+ '@tanstack/table-core@8.21.3': {}
+
'@tanstack/virtual-core@3.13.17': {}
+ '@tanstack/vue-table@8.21.3(vue@3.5.26(typescript@5.9.3))':
+ dependencies:
+ '@tanstack/table-core': 8.21.3
+ vue: 3.5.26(typescript@5.9.3)
+
'@tanstack/vue-virtual@3.13.17(vue@3.5.26(typescript@5.9.3))':
dependencies:
'@tanstack/virtual-core': 3.13.17
@@ -7447,12 +7469,13 @@ snapshots:
dependencies:
prelude-ls: 1.2.1
+ type-fest@4.41.0: {}
+
type-is@2.0.1:
dependencies:
content-type: 1.0.5
media-typer: 1.1.0
mime-types: 3.0.2
- type-fest@4.41.0: {}
typescript-eslint@8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3):
dependencies:
@@ -7549,6 +7572,7 @@ snapshots:
util-deprecate@1.0.2: {}
vary@1.1.2: {}
+
vee-validate@4.15.1(vue@3.5.26(typescript@5.9.3)):
dependencies:
'@vue/devtools-api': 7.7.9