Vue and Nuxt quick start
A complete native Vue table, shared Query setup, and Nuxt integration.
Build the same product table with native Vue configuration and a shared Vue Query client. Install the Vue registry before copying these files. For the shared mental model, read core concepts.
Project structure
products/
shared/products.ts
vue/product-config.ts
vue/product-actions.ts
vue/products.vue
vue/main.ts1. Define records
export interface Product extends Record<string, unknown> {
active: boolean;
id: string;
image: string;
name: string;
price: number;
/** Civil dates, inclusive at both ends: the window a batch is being restocked. */
restockFrom: string;
restockTo: string;
status: "draft" | "active" | "archived";
stock: number;
tags: string[];
updatedAt: string;
}
export const products: Product[] = [
{
id: "desk",
name: "Oak desk",
price: 420,
stock: 12,
status: "active",
active: true,
tags: ["office", "wood"],
image: "https://placehold.co/640x480/png?text=Oak+desk",
restockFrom: "2026-09-14",
restockTo: "2026-09-18",
updatedAt: "2026-09-01T09:00:00Z",
},
{
id: "lamp",
name: "Desk lamp",
price: 65,
stock: 0,
status: "draft",
active: false,
tags: ["office"],
image: "https://placehold.co/640x480/png?text=Desk+lamp",
restockFrom: "2026-09-17",
restockTo: "2026-09-24",
updatedAt: "2026-09-02T09:00:00Z",
},
{
id: "chair",
name: "Studio chair",
price: 180,
stock: 6,
status: "active",
active: true,
tags: ["office", "seating"],
image: "https://placehold.co/640x480/png?text=Studio+chair",
restockFrom: "2026-09-23",
restockTo: "2026-10-02",
updatedAt: "2026-09-03T09:00:00Z",
},
];2. Configure the table
import { defineTableConfig } from "@/components/ui/yayaw-table-vue";
export const productConfig = defineTableConfig({
id: "products",
translations: { namespace: "products", keys: {} },
columns: {
definitions: [
{ id: "name", header: "Name", type: "text" },
{ id: "price", header: "Price", type: "number" },
{ id: "stock", header: "Stock", type: "number" },
{
id: "status",
header: "Status",
type: "select",
options: [
{ label: "Draft", value: "draft" },
{ label: "Active", value: "active" },
{ label: "Archived", value: "archived" },
],
},
],
order: ["name", "price", "stock", "status"],
visible: ["name", "price", "stock", "status"],
mandatory: ["name"],
sort: [{ id: "name", desc: false }],
},
table: {
allowCreate: false,
allowEdit: false,
allowDelete: false,
allowDuplicate: false,
allowBulkEdit: false,
allowBulkDelete: false,
enableRowSelection: false,
enableViews: false,
syncUrl: false,
defaultPageSize: 10,
},
});
export const getTableConfig = (tableType: string) =>
tableType === "products" ? productConfig : undefined;3. Connect list actions
This adapter implements the same request contract as React. Search covers names, references, prices, stock, statuses, booleans and tags. Column filters and sorting apply before pagination, and the result includes totals. It uses fictional in-memory records.
import type { TableActions } from "@/components/ui/yayaw-table-vue/types";
import { products } from "../shared/products";
import { listRecords } from "./list-records";
export const productActions: TableActions = {
list: (input) => listRecords(products, input),
};
export const getTableActions = (tableType: string) =>
tableType === "products" ? productActions : undefined;Complete list adapter
import {
compatibleListParams,
matchesContractFilter,
normalizeFilterEnvelope,
recordValue,
} from "@/components/ui/yayaw-table-vue/table-contracts";
export function listRecords<T extends Record<string, unknown>>(
rows: T[],
input: unknown
) {
const params = compatibleListParams(recordValue(input));
const page = Number(params.page);
const limit = Number(params.limit);
const search = String(params.search).toLowerCase();
const filters = Object.entries(recordValue(params.filters));
const advanced = normalizeFilterEnvelope(params.advancedFilters).filters;
const matching = rows.filter((product) => {
const row: Record<string, unknown> = product;
const matchesSearch =
!search ||
["name", "reference", "price", "stock", "status", "active", "tags"].some(
(key) => {
const value = product[key];
return (
value !== null &&
value !== undefined &&
String(value).toLowerCase().includes(search)
);
}
);
const matchesColumns = filters.every(([id, value]) =>
matchesContractFilter(row[id], {
type: typeof row[id] === "number" ? "number" : "text",
operator: Array.isArray(value) ? "isAnyOf" : "contains",
values: Array.isArray(value) ? value : [value],
})
);
const matchesRule = (rule: Record<string, unknown>) =>
matchesContractFilter(row[String(rule.columnId)], rule);
const matchesAdvanced =
advanced.length === 0 ||
(params.advancedFilterJoin === "or"
? advanced.some(matchesRule)
: advanced.every(matchesRule));
return matchesSearch && matchesColumns && matchesAdvanced;
});
const sorting = Object.entries(recordValue(params.orderBy));
matching.sort((left, right) => {
for (const [id, direction] of sorting) {
const a = (left as Record<string, unknown>)[id];
const b = (right as Record<string, unknown>)[id];
const comparison =
typeof a === "number" && typeof b === "number"
? a - b
: String(a ?? "").localeCompare(String(b ?? ""));
if (comparison !== 0) {
return direction === "desc" ? -comparison : comparison;
}
}
return String(left.id).localeCompare(String(right.id));
});
return Promise.resolve({
// Model a server response: later store mutations must not alter cached rows.
data: structuredClone(matching.slice((page - 1) * limit, page * limit)),
meta: {
pageCount: Math.max(1, Math.ceil(matching.length / limit)),
totalCount: matching.length,
},
});
}4. Render the component
Sample data. Changes stay in this preview.
<script setup lang="ts">
import type { ExamplePresentation } from "./presentation";
defineProps<ExamplePresentation>();
import { YayawDataTable } from "@/components/ui/yayaw-table-vue";
import { getTableActions } from "./product-actions";
import { productConfig } from "./product-config";
</script>
<template>
<YayawDataTable v-bind="$props"
:config="productConfig"
:get-table-actions="getTableActions"
:get-row-id="row => String(row.id)"
table-id="products-example"
table-type="products"
:title="title ?? 'Products'"
/>
</template>5. Install the shared Query client
import { QueryClient, VueQueryPlugin } from "@tanstack/vue-query";
import { createApp } from "vue";
import Products from "./products.vue";
import "@/components/ui/yayaw-table-vue/styles.css";
const app = createApp(Products);
app.use(VueQueryPlugin, { queryClient: new QueryClient() });
app.mount("#app");Mount Products in an element with id="app", or use your application’s existing entry point and provider. Keep the registry styles in the application CSS entry. Vue does not use Nuqs.
Local data
For a local-only table, pass :data="products" and remove get-table-actions. Vue applies filtering, sorting, and pagination to that array. Keep remote lists action-driven; a single server page passed as local data cannot represent the complete dataset.
Nuxt and SSR
Install VueQueryPlugin in a Nuxt plugin with a new QueryClient per application/request. Reuse the project’s dehydration and hydration setup when preloading queries. Import the registry stylesheet from the Nuxt CSS configuration. Keep browser-only custom renderers behind the appropriate client boundary, and use initialData, initialRowCount, and initialPageCount only for the matching initial query.
Application notifications
Mount a single Toaster from vue-sonner at the application root and import vue-sonner/style.css when using the bare Sonner outlet. All table instances share it. Forms retain their inline validation and retry errors.
Continue with a capability
The rest of the documentation is organized by capability. Use the Vue tab in columns, forms, bulk actions, and views. The actions reference explains the request/result aliases shared with React.
Previous guide sections
Vue 3 and Nuxt
Read the current guide: concepts.
Feature coverage
Read the current guide: compatibility.
Runtime requirements
Read the current guide: compatibility.
Run the Vue workspace
Read the current guide: installation.
Migrate from TanStack Table 8
Read the current guide: concepts.
Register the package
Read the current guide: installation.
Configure a table
Read the current guide: configuration.
Table density
Read the current guide: layout.
Catalogue toolbar actions
Read the current guide: actions.
Connect server-backed actions
Read the current guide: actions.
Catalogue forms and submission lifecycle
Read the current guide: forms.
Choose forms by row
Read the current guide: forms.
Sections, translations, and custom controls
Read the current guide: form-layout.
Asynchronous relation options
Read the current guide: concepts.
Collections with isolated item editors
Read the current guide: collections.
Catalogue-backed inline editing
Read the current guide: inline-editing.
Share the Vue Query cache
Read the current guide: query-integration.
Build the shadcn-vue registry item
Read the current guide: installation.
React-to-Vue mapping
Read the current guide: compatibility.
Current React/Vue behavior
Read the current guide: concepts.
One column type across the interface
Read the current guide: concepts.
Structured generated forms
Read the current guide: form-layout.
Grouped rows
Read the current guide: grouping-calculations.
Card controls and empty results
Read the current guide: gallery.
Optional filter bar
Read the current guide: filtering.
Personal favorite views
Read the current guide: views.
Record consultation and activity
Read the current guide: record-details.