Vue Integration
The @toolbox-web/grid-vue package provides Vue 3 integration for the <tbw-grid> data grid component.
Installation
Section titled “Installation”npm install @toolbox-web/grid @toolbox-web/grid-vue
yarn add @toolbox-web/grid @toolbox-web/grid-vuepnpm add @toolbox-web/grid @toolbox-web/grid-vuebun add @toolbox-web/grid @toolbox-web/grid-vueRegister the grid web component and configure Vue to recognize custom elements:
import '@toolbox-web/grid';
// Enable features via side-effect imports// Only import the features you use - this keeps your bundle small!import '@toolbox-web/grid-vue/features/selection';import '@toolbox-web/grid-vue/features/editing';import '@toolbox-web/grid-vue/features/multi-sort';import '@toolbox-web/grid-vue/features/filtering';Important: Configure Vue to treat tbw-* tags as custom elements in your vite.config.ts:
import vue from '@vitejs/plugin-vue';import { defineConfig } from 'vite';
export default defineConfig({ plugins: [ vue({ template: { compilerOptions: { // Tell Vue that tbw-* tags are web components isCustomElement: (tag) => tag.startsWith('tbw-'), }, }, }), ],});Basic Usage
Section titled “Basic Usage”The simplest way to use the grid is with the TbwGrid component and feature props:
<script setup lang="ts">// Enable features you useimport '@toolbox-web/grid-vue/features/selection';import '@toolbox-web/grid-vue/features/editing';import '@toolbox-web/grid-vue/features/multi-sort';import '@toolbox-web/grid-vue/features/filtering';
import { TbwGrid } from '@toolbox-web/grid-vue';import type { ColumnConfig } from '@toolbox-web/grid';import { ref } from 'vue';
interface Employee { id: number; name: string; department: string; salary: number;}
const employees = ref<Employee[]>([ { id: 1, name: 'Alice', department: 'Engineering', salary: 95000 }, { id: 2, name: 'Bob', department: 'Marketing', salary: 75000 }, { id: 3, name: 'Charlie', department: 'Sales', salary: 85000 },]);
const columns: ColumnConfig<Employee>[] = [ { field: 'id', header: 'ID', type: 'number', width: 70 }, { field: 'name', header: 'Name', editable: true, sortable: true }, { field: 'department', header: 'Department', editable: true, sortable: true }, { field: 'salary', header: 'Salary', type: 'number', format: (v: number) => '$' + v.toLocaleString() },];</script>
<template> <TbwGrid :rows="employees" :columns="columns" selection="range" editing="dblclick" :multi-sort="true" :filtering="{ debounceMs: 200 }" style="height: 400px; display: block" /></template>Feature Prop Reference
Section titled “Feature Prop Reference”Each feature prop enables a specific plugin with simplified configuration:
| Feature Prop | Feature Import | Description |
|---|---|---|
selection | features/selection | Cell, row, or range selection |
editing | features/editing | Inline cell editing |
multi-sort | features/multi-sort | Multi-column sorting |
filtering | features/filtering | Column filtering |
clipboard | features/clipboard | Copy/paste support |
context-menu | features/context-menu | Right-click context menu |
reorder-columns | features/reorder-columns | Column drag-to-reorder |
visibility | features/visibility | Column visibility panel |
pinned-columns | features/pinned-columns | Sticky left/right columns |
pinned-rows | features/pinned-rows | Sticky top/bottom rows |
grouping-columns | features/grouping-columns | Multi-level column headers |
grouping-rows | features/grouping-rows | Row grouping |
column-virtualization | features/column-virtualization | Virtualize columns for wide grids |
reorder-rows | features/reorder-rows | Row drag-to-reorder |
tree | features/tree | Hierarchical tree view |
master-detail | features/master-detail | Expandable detail rows |
responsive | features/responsive | Card layout for narrow viewports |
undo-redo | features/undo-redo | Edit undo/redo |
export | features/export | CSV/Excel export |
print | features/print | Print support |
pivot | features/pivot | Pivot table functionality |
server-side | features/server-side | Server-side data loading |
Core Config Props (no feature import needed):
| Prop | Type | Description |
|---|---|---|
sortable | boolean | Grid-wide sorting toggle (default: true) |
filterable | boolean | Grid-wide filtering toggle (default: true). Requires FilteringPlugin. |
selectable | boolean | Grid-wide selection toggle (default: true). Requires SelectionPlugin. |
Custom Cell Renderers
Section titled “Custom Cell Renderers”Use the TbwGridColumn component with the #cell slot to customize how cell values are displayed:
<script setup lang="ts">import { TbwGrid, TbwGridColumn } from '@toolbox-web/grid-vue';import { ref } from 'vue';import StatusBadge from './StatusBadge.vue';
interface Employee { id: number; name: string; status: 'active' | 'inactive' | 'pending';}
const employees = ref<Employee[]>([ { id: 1, name: 'Alice', status: 'active' }, { id: 2, name: 'Bob', status: 'inactive' }, { id: 3, name: 'Charlie', status: 'pending' },]);
const columns = [ { field: 'id', header: 'ID', width: 70 }, { field: 'name', header: 'Name' }, { field: 'status', header: 'Status' },];</script>
<template> <TbwGrid :rows="employees" :columns="columns" style="height: 400px; display: block"> <!-- Custom renderer for the status column --> <TbwGridColumn field="status"> <template #cell="{ value, row }"> <StatusBadge :status="value" /> </template> </TbwGridColumn> </TbwGrid></template>Slot Props:
| Prop | Type | Description |
|---|---|---|
value | TValue | The cell value |
row | TRow | The full row data object |
column | ColumnConfig | Column configuration |
Custom Cell Editors
Section titled “Custom Cell Editors”Use the #editor slot for inline cell editing. The slot provides commit and cancel functions:
<script setup lang="ts">import '@toolbox-web/grid-vue/features/editing';import { TbwGrid, TbwGridColumn } from '@toolbox-web/grid-vue';import { ref } from 'vue';import StatusBadge from './StatusBadge.vue';import StatusSelectEditor from './StatusSelectEditor.vue';
interface Employee { id: number; name: string; status: 'active' | 'inactive' | 'pending';}
const employees = ref<Employee[]>([/* ... */]);
const columns = [ { field: 'id', header: 'ID', width: 70 }, { field: 'name', header: 'Name', editable: true }, { field: 'status', header: 'Status', editable: true },];</script>
<template> <TbwGrid :rows="employees" :columns="columns" editing="dblclick" style="height: 400px; display: block" > <TbwGridColumn field="status"> <template #cell="{ value }"> <StatusBadge :status="value" /> </template> <template #editor="{ value, commit, cancel }"> <StatusSelectEditor :value="value" @commit="commit" @cancel="cancel" /> </template> </TbwGridColumn> </TbwGrid></template>Editor Slot Props:
| Prop | Type | Description |
|---|---|---|
value | TValue | The current cell value |
row | TRow | The full row data object |
column | ColumnConfig | Column configuration |
commit | (value: TValue) => void | Call to save the new value |
cancel | () => void | Call to cancel editing |
Example editor component:
<script setup lang="ts">defineProps<{ value: string;}>();
const emit = defineEmits<{ commit: [value: string]; cancel: [];}>();
function onChange(event: Event) { const target = event.target as HTMLSelectElement; emit('commit', target.value);}</script>
<template> <select :value="value" @change="onChange" @keydown.escape="emit('cancel')"> <option value="active">Active</option> <option value="inactive">Inactive</option> <option value="pending">Pending</option> </select></template>Handling Events
Section titled “Handling Events”Listen to grid events using standard Vue event binding. Events emit CustomEvent objects — access data via event.detail:
<script setup lang="ts">import { TbwGrid } from '@toolbox-web/grid-vue';import type { CellClickDetail, CellCommitDetail, SortChangeDetail } from '@toolbox-web/grid';
function onCellClick(event: CustomEvent<CellClickDetail>) { const { row, column, value } = event.detail; console.log('Cell clicked:', { row, column, value });}
function onCellCommit(event: CustomEvent<CellCommitDetail>) { const { row, column, newValue, oldValue } = event.detail; console.log('Cell edited:', { row, column, newValue, oldValue });}
function onSortChange(event: CustomEvent<SortChangeDetail>) { const { sortState } = event.detail; console.log('Sort changed:', sortState);}</script>
<template> <TbwGrid :rows="employees" :columns="columns" @cell-click="onCellClick" @cell-commit="onCellCommit" @sort-change="onSortChange" /></template>Note: Unlike React (which unwraps
event.detailas the first argument), the Vue adapter passes the fullCustomEventobject to handlers. Access event data viaevent.detail.
Programmatic Grid Access
Section titled “Programmatic Grid Access”Use the useGrid composable for programmatic control:
<script setup lang="ts">import { TbwGrid, useGrid } from '@toolbox-web/grid-vue';import { ref } from 'vue';
interface Employee { id: number; name: string;}
const employees = ref<Employee[]>([/* ... */]);const { gridElement, forceLayout } = useGrid();
function refreshGrid() { forceLayout();}
async function exportCsv() { const grid = gridElement.value; if (!grid) return;
const exportPlugin = grid.getPluginByName('export'); await exportPlugin?.exportToCsv({ filename: 'employees.csv' });}</script>
<template> <div> <button @click="refreshGrid">Refresh</button> <button @click="exportCsv">Export CSV</button> <TbwGrid :rows="employees" :columns="columns" /> </div></template>The useGridEvent Composable
Section titled “The useGridEvent Composable”For programmatic event handling beyond template bindings, use useGridEvent. It provides type-safe event subscription with automatic cleanup:
<script setup lang="ts">import '@toolbox-web/grid-vue/features/editing';import { TbwGrid, useGrid, useGridEvent } from '@toolbox-web/grid-vue';import type { CellCommitDetail } from '@toolbox-web/grid';import { ref } from 'vue';
const editHistory = ref<string[]>([]);const employees = ref<Employee[]>([/* ... */]);const { gridElement } = useGrid();
useGridEvent('cell-commit', (event: CustomEvent<CellCommitDetail<Employee>>) => { const detail = event.detail; editHistory.value.push( `${detail.column.field}: ${detail.oldValue} → ${detail.newValue}` );}, gridElement);</script>
<template> <div> <ul><li v-for="(entry, i) in editHistory" :key="i">{{ entry }}</li></ul> <TbwGrid :rows="employees" :columns="columns" editing="dblclick" /> </div></template>Feature-Scoped Composables
Section titled “Feature-Scoped Composables”Feature imports export scoped composables for type-safe programmatic access to plugin functionality:
<script setup lang="ts">import '@toolbox-web/grid-vue/features/export';import { useGridExport } from '@toolbox-web/grid-vue/features/export';import { TbwGrid } from '@toolbox-web/grid-vue';import { ref } from 'vue';
const employees = ref<Employee[]>([/* ... */]);const { exportToCsv, exportToExcel, isExporting } = useGridExport();</script>
<template> <div> <button @click="exportToCsv('employees.csv')" :disabled="isExporting()">Export CSV</button> <TbwGrid :rows="employees" :columns="columns" export /> </div></template>Available Feature Composables
Section titled “Available Feature Composables”| Composable | Import | Key Methods |
|---|---|---|
useGridSelection() | features/selection | selectAll, clearSelection, getSelection, getSelectedRows |
useGridFiltering() | features/filtering | setFilter, clearAllFilters, getFilters, getFilteredRowCount |
useGridExport() | features/export | exportToCsv, exportToExcel, exportToJson, isExporting |
useGridPrint() | features/print | print, isPrinting |
useGridUndoRedo() | features/undo-redo | undo, redo, canUndo, canRedo |
Manual Plugin Instantiation
Section titled “Manual Plugin Instantiation”For custom configurations or third-party plugins, instantiate manually using markRaw():
<script setup lang="ts">import { TbwGrid, type GridConfig } from '@toolbox-web/grid-vue';import { SelectionPlugin, EditingPlugin, ClipboardPlugin } from '@toolbox-web/grid/all';import { markRaw, ref } from 'vue';
const employees = ref<Employee[]>([/* ... */]);
const config = markRaw<GridConfig<Employee>>({ columns: [ { field: 'id', header: 'ID' }, { field: 'name', header: 'Name', editable: true }, ], plugins: [ new SelectionPlugin({ mode: 'range', checkbox: true }), new EditingPlugin({ editOn: 'dblclick' }), new ClipboardPlugin({ includeHeaders: true }), ],});</script>
<template> <TbwGrid :rows="employees" :grid-config="config" /></template>Important: Always use
markRaw()for grid configs containing plugins. Vue’s reactivity system can interfere with plugin class instances.
Feature props and manual plugins can be mixed — the grid merges them.
Custom Icons
Section titled “Custom Icons”Use GridIconProvider to override grid icons application-wide:
<script setup lang="ts">import { GridIconProvider, TbwGrid } from '@toolbox-web/grid-vue';</script>
<template> <GridIconProvider :icons="{ sortAsc: '▲', sortDesc: '▼' }"> <TbwGrid :rows="employees" :columns="columns" /> </GridIconProvider></template>Responsive Card Layout
Section titled “Responsive Card Layout”Use TbwGridResponsiveCard for mobile-friendly card layouts:
<script setup lang="ts">import '@toolbox-web/grid-vue/features/responsive';import { TbwGrid, TbwGridResponsiveCard } from '@toolbox-web/grid-vue';import { ref } from 'vue';
const employees = ref<Employee[]>([/* ... */]);</script>
<template> <TbwGrid :rows="employees" :columns="columns" :responsive="{ breakpoint: 768, cardTitleField: 'name' }"> <TbwGridResponsiveCard v-slot="{ row }"> <div class="card"> <h3>{{ row.name }}</h3> <p>{{ row.department }} · ${{ row.salary.toLocaleString() }}</p> </div> </TbwGridResponsiveCard> </TbwGrid></template>Server-Side Data Loading
Section titled “Server-Side Data Loading”<script setup lang="ts">import '@toolbox-web/grid-vue/features/multi-sort';import { TbwGrid } from '@toolbox-web/grid-vue';import type { SortState, SortChangeDetail } from '@toolbox-web/grid';import { ref, watch } from 'vue';
const loading = ref(true);const employees = ref<Employee[]>([]);const sortState = ref<SortState[]>([]);
async function fetchData(sort: SortState[]) { loading.value = true; try { const params = new URLSearchParams(); if (sort.length > 0) { params.set('sortField', sort[0].field); params.set('sortDir', sort[0].direction); } const response = await fetch(`/api/employees?${params}`); const { data } = await response.json(); employees.value = data; } finally { loading.value = false; }}
watch(sortState, (newSort) => fetchData(newSort), { immediate: true });
function handleSortChange(event: CustomEvent<SortChangeDetail>) { sortState.value = event.detail.sortState;}</script>
<template> <TbwGrid :rows="employees" :columns="columns" :multi-sort="{ mode: 'external', sortState: sortState }" @sort-change="handleSortChange" /></template>VueQuery (TanStack Query) Integration
Section titled “VueQuery (TanStack Query) Integration”<script setup lang="ts">import '@toolbox-web/grid-vue/features/editing';import { TbwGrid } from '@toolbox-web/grid-vue';import { useQuery, useMutation, useQueryClient } from '@tanstack/vue-query';import type { CellCommitDetail } from '@toolbox-web/grid';
const queryClient = useQueryClient();
const { data: employees, isLoading } = useQuery({ queryKey: ['employees'], queryFn: () => fetch('/api/employees').then(r => r.json()),});
const updateMutation = useMutation({ mutationFn: (employee: Partial<Employee>) => fetch(`/api/employees/${employee.id}`, { method: 'PATCH', body: JSON.stringify(employee), }), onSuccess: () => queryClient.invalidateQueries({ queryKey: ['employees'] }),});
function handleCellCommit(event: CustomEvent<CellCommitDetail<Employee>>) { const detail = event.detail; updateMutation.mutate({ id: detail.row.id, [detail.column.field as keyof Employee]: detail.newValue, });}</script>
<template> <div v-if="isLoading">Loading...</div> <TbwGrid v-else :rows="employees ?? []" :columns="columns" editing="dblclick" @cell-commit="handleCellCommit" /></template>Dynamic Row Updates
Section titled “Dynamic Row Updates”<script setup lang="ts">import { TbwGrid } from '@toolbox-web/grid-vue';import { ref } from 'vue';
const employees = ref<Employee[]>([ { id: 1, name: 'Alice', department: 'Engineering' }, { id: 2, name: 'Bob', department: 'Design' },]);
let nextId = 3;
function handleAdd() { employees.value = [ ...employees.value, { id: nextId++, name: 'New Employee', department: 'TBD' }, ];}
function removeFirst() { employees.value = employees.value.slice(1);}</script>
<template> <div> <button @click="handleAdd">Add</button> <button @click="removeFirst">Remove First</button> <TbwGrid :rows="employees" :columns="columns" /> </div></template>Performance Tips
Section titled “Performance Tips”Use markRaw for Config Objects
Section titled “Use markRaw for Config Objects”import { markRaw } from 'vue';
// ✅ Prevents Vue from making config deeply reactiveconst config = markRaw({ columns: [...], features: { selection: true } });Use shallowRef for Large Datasets
Section titled “Use shallowRef for Large Datasets”import { shallowRef } from 'vue';
// ✅ Only tracks reference changes, not deep mutationsconst employees = shallowRef<Employee[]>([]);employees.value = [...employees.value, newEmployee];Memoize Column Configurations
Section titled “Memoize Column Configurations”import { computed, markRaw } from 'vue';
// ✅ Only recreated when dependencies changeconst columns = computed(() => markRaw([ { field: 'id', header: 'ID' }, { field: 'name', header: 'Name' },]));Troubleshooting
Section titled “Troubleshooting”Feature prop not working
Section titled “Feature prop not working”Ensure you imported the feature side-effect in your main.ts:
import '@toolbox-web/grid-vue/features/selection';tbw-grid not recognized as a component
Section titled “tbw-grid not recognized as a component”Add the isCustomElement config to your Vite setup:
vue({ template: { compilerOptions: { isCustomElement: (tag) => tag.startsWith('tbw-'), }, },})Row updates not reflected
Section titled “Row updates not reflected”The grid detects updates via reference equality. Always assign a new array:
// ✅ New reference — grid re-rendersemployees.value = [...employees.value, newRow];
// ❌ Same reference — grid won't detect changeemployees.value.push(newRow);See Also
Section titled “See Also”- API Reference — Complete API documentation for all components, composables, and types
- Grid Plugins — All available plugins
- Core Features — Variable row heights, events, column config
- Common Patterns — Reusable recipes