@tuquet/vue-table
Enterprise, remote-driven Data Table system for Vue 3, powered by TanStack Table and Shadcn-Vue (@tuquet/vue-ui).
๐จ Live Interactive Storybook: https://storybook.tuquet.com/
โจ Features
- Headless + UI Complete: Seamless bridge between headless TanStack Table logic and polished Shadcn-Vue UI components.
- Server-side Orchestration: Remote filtering, remote sorting, remote pagination, and debounced search with automatic request cancellation via
AbortController. - Bi-directional URL Sync: Two-way synchronization between table state (pagination, sorting, filters, search) and browser URL query strings for deep linking and shareable URLs.
- Pluggable Query Adapters: Comes out-of-the-box with
StandardRestAdapter(standard REST?page=1&limit=20&sort=-createdAt&status=active,pending&q=keyword) andLhsBracketsAdapter(NestJS/Strapifilter[status][in]=...), or customize your own in 1 line. - Declarative Faceted Filters: Multi-select category filters with search inside options, badges, and counts built on Radix Popover and Command.
- Zero-Boilerplate DX: Connect any backend endpoint to a fully functional table with search, filter, sort, pagination, skeletons, and empty state in under 30 lines of code.
๐ฆ Installation
pnpm add @tuquet/vue-table @tuquet/vue-ui @tanstack/vue-table
Ensure global styles are imported in your app entry (main.ts):
import '@tuquet/vue-ui/style.css';
๐ Quickstart Example
<script setup lang="ts">
import { h } from 'vue';
import { DataTable, useRemoteTable, type ColumnDef } from '@tuquet/vue-table';
import { Badge } from '@tuquet/vue-ui';
interface User {
id: string;
name: string;
email: string;
role: string;
status: 'active' | 'suspended';
createdAt: string;
}
const columns: ColumnDef<User>[] = [
{ accessorKey: 'id', header: 'ID' },
{ accessorKey: 'name', header: 'Full Name', enableSorting: true },
{ accessorKey: 'email', header: 'Email' },
{
accessorKey: 'status',
header: 'Status',
cell: ({ row }) =>
h(
Badge,
{ variant: row.original.status === 'active' ? 'default' : 'secondary' },
() => row.original.status
),
},
];
const remote = useRemoteTable<User>({
columns,
fetcher: async ({ toQueryString }) => {
const res = await fetch(`/api/users?${toQueryString()}`);
return res.json(); // returns { data: User[], total: number }
},
defaultPageSize: 10,
syncWithUrl: true,
filters: [
{
id: 'status',
title: 'Status',
type: 'faceted',
options: [
{ label: 'Active', value: 'active' },
{ label: 'Suspended', value: 'suspended' },
],
},
],
});
</script>
<template>
<div class="p-6">
<DataTable :remote="remote" />
</div>
</template>
๐ Components Included
| Component | Description |
|---|---|
<DataTable> |
Container component orchestrating toolbar, header, body with loading skeletons, empty state, error retry, and pagination. |
<DataTableToolbar> |
Search input, dynamic faceted filter triggers, reset button, and custom action slots (#actions). |
<DataTableFloatingBar> |
Floating bulk action bar animated at bottom with selection count badge, #actions slot, and Clear (Esc) trigger. |
<DataTableRowActions> |
Dropdown action menu (3 dots) with custom actions, icons, separators, and destructive variants. |
<DataTablePagination> |
Page navigation (first, prev, next, last), rows per page selector (10/20/50/100), selection summary. |
<DataTableColumnHeader> |
Sortable column header button with Asc / Desc / Clear icons and hide column menu. |
<DataTableFacetedFilter> |
Multi-select category popover with command search, item checkboxes, and count badges. |
<DataTableSelectFilter> |
Single-select filter dropdown menu with Radio group, clear button, and custom option icons. |
<DataTableDateRangeFilter> |
Date range picker popover with quick presets (Today, Yesterday, Last 7d, Last 30d, etc.) and dual-month <RangeCalendar>. |
<DataTableFilterBuilder> |
Advanced database-grade filter builder (Airtable/Notion/Supabase style) with operator switching, AND/OR conjunctions, inline chips, and presets. |
<DataTableViewOptions> |
Dropdown menu to toggle column visibility. |
<CopyableCell> |
Inline cell rendering with click-to-copy button and checkmark confirmation. |
๐ Row Selection & Bulk Actions
Easily add row selection with createSelectionColumn() and bulk action toolbar:
import { createSelectionColumn, type ColumnDef } from '@tuquet/vue-table';
const columns: ColumnDef<User>[] = [
createSelectionColumn<User>({
isRowSelectable: (row) => row.original.status !== 'suspended', // optional guard
}),
{ accessorKey: 'name', header: 'Name' },
// ...
];
In your template, pass bulk action buttons into the #bulk-actions slot:
<DataTable :remote="remote">
<template #bulk-actions="{ selectedRows, selectedCount }">
<Button variant="destructive" size="sm" @click="handleBulkDelete(selectedRows)">
Delete ({{ selectedCount }})
</Button>
</template>
</DataTable>
๐จ Pre-built Column Formatters
Build clean, consistent table columns in seconds with typed helpers:
import {
createSelectionColumn,
createDateColumn,
createBadgeColumn,
createCurrencyColumn,
createCopyableColumn,
createActionsColumn,
type ColumnDef,
} from '@tuquet/vue-table';
const columns: ColumnDef<Order>[] = [
createSelectionColumn(),
createCopyableColumn({ accessorKey: 'orderNumber', header: 'Order #' }),
createCurrencyColumn({ accessorKey: 'total', header: 'Total', currency: 'USD' }),
createBadgeColumn({
accessorKey: 'status',
header: 'Status',
variants: {
completed: 'default',
pending: 'secondary',
cancelled: 'destructive',
},
}),
createDateColumn({
accessorKey: 'createdAt',
header: 'Ordered At',
relative: true, // "5m ago", "yesterday", etc.
}),
createActionsColumn({
actions: [
{ id: 'view', label: 'View Details', onSelect: (row) => viewOrder(row.original) },
{
id: 'delete',
label: 'Delete',
variant: 'destructive',
separator: true,
onSelect: (row) => remote.deleteRow(row.original.id),
},
],
}),
];
๐ Optimistic CRUD Local Mutations
Pair @tuquet/vue-table with @tuquet/vue-form or any modal/sheet for instant UI updates without slow network refetches:
// Update a record in-place after modal edit
remote.mutateRow(orderId, { status: 'completed' });
// Delete row(s) and automatically decrement total
remote.deleteRow(orderId);
remote.deleteRow([orderId1, orderId2]); // bulk delete
// Prepend newly created item from drawer to top
remote.prependRow(newOrder);
// Force refresh from server when needed
await remote.refetch();
๐ค Multi-format Data Export (CSV, TSV & Excel .xlsx)
Export table data with automatic Excel UTF-8 BOM encoding or native styled Excel spreadsheets (.xlsx) via write-excel-file:
import { exportToCsv, exportToExcel, copyToClipboardAsTsv } from '@tuquet/vue-table';
// 1. Download formatted Excel spreadsheet (.xlsx) with header styling and typed columns
await exportToExcel({
data: remote.data.value,
columns,
filename: 'orders-export.xlsx',
headerStyle: {
fontWeight: 'bold',
backgroundColor: '#e2e8f0',
},
});
// 2. Download CSV with UTF-8 BOM
exportToCsv({
data: remote.data.value,
columns,
filename: 'orders-export.csv',
});
// 3. Or copy selected rows for pasting into Google Sheets / Excel
await copyToClipboardAsTsv({
data: remote.selectedRows.value,
columns,
});
โก Virtual Scrolling for Large Datasets
When rendering datasets with thousands of rows without pagination (or with large page sizes), enable high-performance virtualization powered by @tanstack/vue-virtual:
<DataTable
:remote="remote"
:virtual="true"
virtual-height="550px"
:estimated-row-height="44"
:overscan="5"
/>
Features:
- Dynamically measures rendered rows with dynamic heights.
- Retains sticky headers and column pinning seamlessly during scroll.
- Zero DOM lag even with 10,000+ client-side records.
๐ Query Adapters
1. StandardRestAdapter (Default)
Generates query string:
?page=1&limit=10&sort=-createdAt,name&status=active,pending&q=keyword
2. LhsBracketsAdapter
Generates query string:
?page=1&limit=10&sort[createdAt]=desc&filter[status][in]=active,pending&filter[q]=keyword
3. SpringPageableAdapter
For Spring Boot / Spring Data REST endpoints:
?page=0&size=20&sort=createdAt,desc&sort=name,asc&q=keyword
import { SpringPageableAdapter, useRemoteTable } from '@tuquet/vue-table';
const remote = useRemoteTable({
adapter: new SpringPageableAdapter(),
// ...
});
4. Custom Adapter with createCustomAdapter()
Create custom query adapters in seconds:
import { createCustomAdapter, useRemoteTable } from '@tuquet/vue-table';
const myAdapter = createCustomAdapter({
name: 'my-custom',
serialize: (state) => ({
offset: state.pagination.pageIndex * state.pagination.pageSize,
count: state.pagination.pageSize,
}),
deserialize: (query) => ({
pagination: {
pageIndex: Math.floor(Number(query.offset || 0) / Number(query.count || 10)),
pageSize: Number(query.count || 10),
},
}),
});
๐ Advanced Date Range Filtering
Filter records within date intervals using <DataTableDateRangeFilter>:
<template>
<DataTable :remote="remote">
<template #filters>
<DataTableDateRangeFilter
title="Created Date"
:model-value="remote.filters.value.createdAt"
@update:model-value="(val) => remote.setFilter('createdAt', val)"
/>
</template>
</DataTable>
</template>
Features:
- Built-in shortcuts: Today, Yesterday, Last 7 days, Last 30 days, This month, Last month.
- Dual-month interactive calendar.
- Automatically serializes to
_startand_end(StandardRestAdapter) or[gte]and[lte](LhsBracketsAdapter).
๐ Sticky Column Pinning & Ergonomics
Freeze columns on the left (e.g. selection checkboxes, customer name) or right (e.g. row actions):
const remote = useRemoteTable({
columns,
columnPinning: {
left: ['select'],
right: ['actions'],
},
// ...
});
Sticky pinned columns automatically receive boundary elevation shadows and blurred background isolation during horizontal scrolling.
๐ง Flexible Enterprise Database Filter System (Airtable / Notion style)
Build arbitrary compound queries with dynamic column selection, data-type aware operators, and logical conjunctions (AND / OR):
<script setup lang="ts">
import { ref } from 'vue';
import {
DataTable,
DataTableFilterBuilder,
useRemoteTable,
useDynamicFilters,
type ColumnFilterDefinition,
type DynamicFilterRule,
} from '@tuquet/vue-table';
const columnDefs: ColumnFilterDefinition[] = [
{ id: 'customer', label: 'Customer', dataType: 'text' },
{ id: 'total', label: 'Total ($)', dataType: 'number' },
{
id: 'status',
label: 'Status',
dataType: 'select',
options: [
{ label: 'Completed', value: 'completed' },
{ label: 'Pending', value: 'pending' },
{ label: 'Cancelled', value: 'cancelled' },
],
},
{ id: 'createdAt', label: 'Created At', dataType: 'date' },
];
const dynamicRules = ref<DynamicFilterRule[]>([]);
const conjunction = ref<'and' | 'or'>('and');
// Connect to remote or in-memory evaluation
</script>
<template>
<DataTable
:remote="remote"
:show-filter-builder="true"
:column-filter-defs="columnDefs"
:dynamic-rules="dynamicRules"
:conjunction="conjunction"
@update:dynamic-rules="
(r) => {
dynamicRules = r;
remote.refetch();
}
"
@update:conjunction="
(c) => {
conjunction = c;
remote.refetch();
}
"
/>
</template>
Supported Operators by Data Type
| Data Type | Supported Operators |
|---|---|
text |
contains, notContains, eq (=), ne (!=), startsWith, endsWith, isEmpty, isNotEmpty |
number |
eq (=), ne (!=), gt (>), gte (>=), lt (<), lte (<=), between (Range min-max), isEmpty, isNotEmpty |
select |
is, isNot, in (In list), notIn (Not in list), isEmpty, isNotEmpty |
date |
is, isNot, before, after, between (Date range), isEmpty, isNotEmpty |
boolean |
isTrue, isFalse |
In-Memory Evaluation Engine
Evaluate dynamic filter rules client-side with zero dependencies:
import { filterDataset, evaluateFilterRule } from '@tuquet/vue-table';
const filteredOrders = filterDataset(allOrders, dynamicRules.value, 'and');
๐ Ecosystem
Part of the Automation & Agent Ecosystem:
- Automa โ Native Chrome/Edge Desktop UI Automation Browser.
- Runner โ High-Performance Distributed Process Supervision Engine in Rust.
- Browser โ High-Performance Headless Web Scraping & Stealth Automation Core.
- Cloud โ Enterprise Orchestration & Real-time Task Control Plane.
- CLI โ Developer Ergonomic CLI & Unified Command Center.
- Lib โ Monorepo for Shared Enterprise UI & Utilities (
vue-ui,vue-table,md-export,extension-runner,lunar).
๐ License
Distributed under the MIT License.
Related Packages
Vue-native on TanStack Table and TanStack Virtual: sorting, filtering, paginatio...
Idiomatic Vue headless WAI-ARIA data table (sorting, filtering, pagination, sele...
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 0.2.2 | ^3.4.0 || | MIT | |
| 0.2.1 | ^3.4.0 || | MIT | |
| 0.2.0 | ^3.4.0 || | MIT |