@toolbox-web/grid-vue

Vue 3 adapter for @toolbox-web/grid data grid component
9,169 56
Install
npm install @toolbox-web/grid-vue
Latest Version:2.6.3
Vue:3
License:MIT
Last Updated:Oct 23, 2026
Links: GitHub  ·  npm
Maintainer: oystein.amundsen

@toolbox-web/grid-vue

npm License: MIT GitHub Sponsors

Vue 3 adapter for @toolbox-web/grid data grid component. Provides components and composables for declarative Vue integration with custom cell renderers and editors.

Features

  • ✅ Full Vue 3 integration - Use Vue components for cell renderers and editors
  • ✅ Composition API - Built with <script setup> and TypeScript generics
  • ✅ Declarative feature props - Enable plugins with simple props like selection="range"
  • ✅ Tree-shakeable features - Only import the features you use
  • ✅ Slot-based customization - #cell and #editor slots for custom rendering
  • ✅ Type-level defaults - App-wide renderers/editors via GridTypeProvider
  • ✅ Icon configuration - App-wide icon overrides via GridProvider or GridIconProvider
  • ✅ Composables - useGrid for programmatic access
  • ✅ Master-detail - TbwGridDetailPanel for expandable rows
  • ✅ Tool panels - TbwGridToolPanel for custom sidebar content
  • ✅ Responsive cards - TbwGridResponsiveCard for mobile layouts
  • ✅ Full type safety - TypeScript generics support
  • ✅ Vue 3.4+ - Supports defineModel and improved generics

Installation

# npm
npm install @toolbox-web/grid @toolbox-web/grid-vue

# yarn
yarn add @toolbox-web/grid @toolbox-web/grid-vue

# pnpm
pnpm add @toolbox-web/grid @toolbox-web/grid-vue

# bun
bun add @toolbox-web/grid @toolbox-web/grid-vue

Quick Start

1. Register the Grid Component

In your application entry point, import the grid registration:

// main.ts
import '@toolbox-web/grid';

2. Use in Components

<script setup lang="ts">
import { TbwGrid, TbwGridColumn } from '@toolbox-web/grid-vue';
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 },
]);
</script>

<template>
  <TbwGrid :rows="employees">
    <TbwGridColumn field="id" header="ID" :width="60" />
    <TbwGridColumn field="name" header="Name" :sortable="true" />
    <TbwGridColumn field="department" header="Department" :sortable="true" />
    <TbwGridColumn field="salary" header="Salary" type="number" />
  </TbwGrid>
</template>

TbwGrid Props

Prop Type Description
rows TRow[] Row data to display
columns ColumnConfig[] Column definitions
gridConfig GridConfig Full configuration object
fitMode 'stretch' | 'fixed' Column sizing mode (default: 'stretch')
sortable boolean Grid-wide sorting toggle (default: true)
filterable boolean Grid-wide filtering toggle (default: true)
selectable boolean Grid-wide selection toggle (default: true)
loading boolean Show loading overlay (default: false)
customStyles string CSS injected via document.adoptedStyleSheets

Enabling Features

Features are enabled using declarative props with side-effect imports. This gives you the best of both worlds: clean, intuitive templates and tree-shakeable bundles.

How It Works

  1. Import the feature - A side-effect import registers the feature factory
  2. Use the prop - TbwGrid detects the prop and creates the plugin instance
<script setup lang="ts">
import { TbwGrid } from '@toolbox-web/grid-vue';

// 1. Import features you need (once, typically in main.ts or the component file)
import '@toolbox-web/grid-vue/features/selection';
import '@toolbox-web/grid-vue/features/multi-sort';
import '@toolbox-web/grid-vue/features/filtering';
</script>

<template>
  <!-- 2. Use declarative props - no manual plugin instantiation! -->
  <TbwGrid :rows="employees" :columns="columns" selection="range" multi-sort filtering />
</template>

Why Side-Effect Imports?

  • Tree-shakeable - Only the features you import are bundled
  • Synchronous - No loading states, no HTTP requests, no spinners
  • Type-safe - Full TypeScript support for feature props
  • Clean templates - No plugins: [new SelectionPlugin({ mode: 'range' })] boilerplate

Available Features

Import from @toolbox-web/grid-vue/features/<name>:

Feature Prop Example
selection selection selection="range" or :selection="{ mode: 'row', checkbox: true }"
multi-sort multi-sort multi-sort or :multi-sort="{ maxSortLevels: 3 }"
filtering filtering filtering or :filtering="{ debounceMs: 200 }"
editing editing editing="dblclick" or editing="click"
clipboard clipboard clipboard (requires selection)
undoRedo undoRedo undoRedo (requires editing — emits separate @undo/@redo events)
context-menu context-menu context-menu
reorder reorder reorder (column drag-to-reorder)
row-reorder row-reorder row-reorder (row drag-to-reorder)
visibility visibility visibility (column visibility panel)
pinned-columns pinned-columns pinned-columns
pinned-rows pinned-rows pinned-rows
grouping-columns grouping-columns grouping-columns
grouping-rows grouping-rows :grouping-rows="{ groupBy: 'department' }"
tree tree :tree="{ childrenField: 'children' }"
column-virtualization column-virtualization column-virtualization
export export export
print print print
responsive responsive responsive (card layout on mobile)
master-detail master-detail master-detail (use with <TbwGridDetailPanel>)
pivot pivot :pivot="{ rowFields: [...], columnFields: [...] }"
server-side server-side :server-side="{ ... }"

Import All Features

For prototyping or when bundle size isn't critical, import all features at once:

// Import all features (larger bundle)
import '@toolbox-web/grid-vue/features';

Custom Cell Renderers

Use the #cell slot on TbwGridColumn for custom rendering. Type the column with your row shape via the generic <TbwGridColumn<Employee>> syntax — slot props (row, value) then narrow automatically with no per-template annotation needed:

<script setup lang="ts">
import { TbwGrid, TbwGridColumn } from '@toolbox-web/grid-vue';
import StatusBadge from './StatusBadge.vue';
interface Employee {
  id: string;
  name: string;
  status: 'active' | 'inactive';
}
</script>

<template>
  <TbwGrid<Employee> :rows="employees">
    <TbwGridColumn<Employee> field="name" header="Name" />
    <TbwGridColumn<Employee> field="status" header="Status">
      <template #cell="{ value, row }">
        <!-- row: Employee, value: any (per-column) -->
        <StatusBadge :status="value" :employee="row" />
      </template>
    </TbwGridColumn>
  </TbwGrid>
</template>

If you also know the value type, pass both generics: <TbwGridColumn<Employee, string>> (or annotate the slot prop with CellSlotProps<Employee, string>).

Slot Props

The #cell slot receives:

Prop Type Description
value TValue Cell value
row TRow Row data
column object Column configuration
rowIndex number Row index
colIndex number Column index

Generic defaults: CellSlotProps<TRow = unknown, TValue = any> and TbwGridColumn<TRow = unknown, TValue = any>. Specify TRow on the column (e.g. <TbwGridColumn<Employee>>) and slot props inherit it automatically; TValue defaults to any because the value shape is per-column and awkward to narrow inside template expressions.

Custom Cell Editors

Use the #editor slot for inline editing. Same generic-typing pattern as #cell:

<script setup lang="ts">
import { TbwGrid, TbwGridColumn } from '@toolbox-web/grid-vue';
import '@toolbox-web/grid-vue/features/editing';
</script>

<template>
  <TbwGrid<Employee> :rows="employees" editing="dblclick">
    <TbwGridColumn<Employee> field="status" header="Status" :editable="true">
      <template #editor="{ value, commit, cancel }">
        <!-- value: any, commit: (newValue: any) => void -->
        <select :value="value" @change="(e) => commit((e.target as HTMLSelectElement).value)">
          <option value="active">Active</option>
          <option value="inactive">Inactive</option>
        </select>
      </template>
    </TbwGridColumn>
  </TbwGrid>
</template>

Editor Slot Props

Prop Type Description
value TValue Current cell value
row TRow Row data
commit (value: any) => void Save the new value
cancel () => void Cancel editing

Generic defaults: EditorSlotProps<TRow = unknown, TValue = any> — same rationale as CellSlotProps above. Specify TRow for row-shape safety; pass TValue when you know the column's value type.

Events

Handle grid events using Vue's template syntax.

Template Event Binding

<script setup lang="ts">
import { TbwGrid } from '@toolbox-web/grid-vue';
import type { CellClickDetail, SortChangeDetail } from '@toolbox-web/grid';

function onCellClick(e: CustomEvent<CellClickDetail>) {
  console.log(`Clicked ${e.detail.field} at row ${e.detail.rowIndex}`);
}

function onSortChange(e: CustomEvent<SortChangeDetail>) {
  console.log(`Sorted by ${e.detail.field} ${e.detail.direction}`);
}
</script>

<template>
  <TbwGrid :rows="rows" @cell-click="onCellClick" @sort-change="onSortChange">
    <TbwGridColumn field="name" header="Name" sortable />
  </TbwGrid>
</template>

Available Events

Event Detail Type Description
@cell-click CellClickDetail Cell was clicked
@row-click RowClickDetail Row was clicked
@cell-activate CellActivateDetail Cell activated (cancelable)
@cell-change CellChangeDetail Row updated via API
@cell-commit CellCommitDetail Cell value committed (cancelable)
@row-commit RowCommitDetail Row edit completed (cancelable)
@changed-rows-reset ChangedRowsResetDetail Changed rows state was reset
@sort-change SortChangeDetail Sort state changed
@filter-change FilterChangeDetail Filter state changed
@column-resize ColumnResizeDetail Column was resized
@column-move ColumnMoveDetail Column was reordered
@column-visibility ColumnVisibilityDetail Column visibility toggled
@column-state-change GridColumnState Column visibility/order changed
@selection-change SelectionChangeDetail Selection state changed
@row-move RowMoveDetail Row was reordered
@group-toggle GroupToggleDetail Row group expanded/collapsed
@group-load-error GroupLoadErrorDetail Server group rows failed to load
@tree-expand TreeExpandDetail Tree node expanded/collapsed
@detail-expand DetailExpandDetail Master-detail row toggled
@detail-load-error DetailLoadErrorDetail Server detail data failed to load
@responsive-change ResponsiveChangeDetail Responsive layout mode changed
@copy CopyDetail Data copied to clipboard
@paste PasteDetail Data pasted from clipboard
@undo UndoRedoDetail Undo action performed
@redo UndoRedoDetail Redo action performed
@export-complete ExportCompleteDetail Export operation completed
@print-start PrintStartDetail Print operation started
@print-complete PrintCompleteDetail Print operation completed

Using Plugins (Advanced)

For full control, pass plugin instances directly via :grid-config. Use markRaw() to prevent Vue from making plugin instances reactive:

<script setup lang="ts">
import { markRaw } from 'vue';
import { TbwGrid, TbwGridColumn } from '@toolbox-web/grid-vue';
import { SelectionPlugin } from '@toolbox-web/grid/plugins/selection';
import { FilteringPlugin } from '@toolbox-web/grid/plugins/filtering';

const gridConfig = {
  plugins: markRaw([new SelectionPlugin({ mode: 'range' }), new FilteringPlugin({ debounceMs: 200 })]),
};
</script>

<template>
  <TbwGrid :rows="rows" :grid-config="gridConfig">
    <TbwGridColumn field="name" header="Name" />
  </TbwGrid>
</template>

Important: Always wrap plugin arrays with markRaw(). Vue's reactivity proxy breaks plugin internal state.

You can also pass plugins through the dedicated :plugins prop, matching <DataGrid plugins={...}> in React and [plugins] in Angular. When :plugins is set, feature props are ignored — the array is the full plugin list:

<script setup lang="ts">
import { markRaw } from 'vue';
import { SelectionPlugin } from '@toolbox-web/grid/plugins/selection';

const plugins = markRaw([new SelectionPlugin({ mode: 'range' })]);
</script>

<template>
  <TbwGrid :rows="rows" :plugins="plugins" />
</template>

Composables

useGrid

Access grid methods and state programmatically:

<script setup lang="ts">
import { useGrid } from '@toolbox-web/grid-vue';

const {
  isReady,
  config,
  forceLayout,
  getConfig,
  ready,
  getPlugin,
  toggleGroup,
  registerStyles,
  unregisterStyles,
  getVisibleColumns,
} = useGrid();
</script>
Property / Method Type Description
isReady Ref<boolean> Reactive flag — true once grid is ready
config Ref<GridConfig | null> Reactive effective grid configuration
element Ref<DataGridElement | null> Raw grid element reference
gridElement Ref<DataGridElement | null> Deprecated alias for element
ready() () => Promise<void> Wait for grid to finish initializing
forceLayout() () => Promise<void> Force layout recalculation
getConfig() () => Promise<GridConfig | null> Get effective configuration snapshot
getPlugin(pluginClass) <T>(cls: new (...) => T) => T | undefined Get plugin by class
getPluginByName(name) (name: string) => Plugin | undefined Get plugin by name
toggleGroup(key) (key: string) => Promise<void> Toggle group expansion
registerStyles(id, css) (id: string, css: string) => void Register custom stylesheet
unregisterStyles(id) (id: string) => void Remove custom stylesheet
getVisibleColumns() () => ColumnConfig[] Get non-hidden columns

Providers

GridTypeProvider

Define app-wide type renderers:

<script setup lang="ts">
import { GridTypeProvider, type TypeDefaultsMap } from '@toolbox-web/grid-vue';
import { h } from 'vue';
import CurrencyCell from './CurrencyCell.vue';

const typeDefaults: TypeDefaultsMap = {
  currency: {
    renderer: (ctx) => h(CurrencyCell, { value: ctx.value }),
  },
  percentage: {
    renderer: (ctx) => h('span', `${(ctx.value * 100).toFixed(1)}%`),
  },
};
</script>

<template>
  <GridTypeProvider :defaults="typeDefaults">
    <App />
  </GridTypeProvider>
</template>

Then use in columns:

<TbwGridColumn field="salary" header="Salary" type="currency" />

GridIconProvider

Override grid icons app-wide:

<script setup lang="ts">
import { GridIconProvider } from '@toolbox-web/grid-vue';

const icons = {
  sortAsc: '↑',
  sortDesc: '↓',
  expand: '+',
  collapse: '−',
  filter: '🔍',
};
</script>

<template>
  <GridIconProvider :icons="icons">
    <App />
  </GridIconProvider>
</template>

GridProvider

Combined provider for both types and icons:

<script setup lang="ts">
import { GridProvider } from '@toolbox-web/grid-vue';
</script>

<template>
  <GridProvider :type-defaults="typeDefaults" :icons="icons">
    <App />
  </GridProvider>
</template>

Master-Detail

Expandable row details:

<script setup lang="ts">
import { TbwGrid, TbwGridDetailPanel, type DetailPanelContext } from '@toolbox-web/grid-vue';
import '@toolbox-web/grid-vue/features/master-detail';
</script>

<template>
  <TbwGrid :rows="employees" master-detail>
    <TbwGridColumn field="name" header="Name" />
    <TbwGridColumn field="department" header="Department" />

    <TbwGridDetailPanel v-slot="{ row }: DetailPanelContext<Employee>">
      <div class="detail-content">
        <h4>{{ row.name }}</h4>
        <p>Department: {{ row.department }}</p>
        <p>Email: {{ row.email }}</p>
      </div>
    </TbwGridDetailPanel>
  </TbwGrid>
</template>

Tool Panels

Custom sidebar panels:

<script setup lang="ts">
import { TbwGrid, TbwGridToolPanel, type ToolPanelContext } from '@toolbox-web/grid-vue';
</script>

<template>
  <TbwGrid :rows="employees">
    <TbwGridColumn field="name" header="Name" />

    <TbwGridToolPanel id="stats" title="Statistics" v-slot="{ rows }: ToolPanelContext<Employee>">
      <div class="stats-panel">
        <p>Total: {{ rows.length }}</p>
        <p>Avg Salary: {{ averageSalary(rows) }}</p>
      </div>
    </TbwGridToolPanel>
  </TbwGrid>
</template>

Responsive Cards

Mobile-friendly card layout:

<script setup lang="ts">
import { TbwGrid, TbwGridResponsiveCard, type ResponsiveCardContext } from '@toolbox-web/grid-vue';
import '@toolbox-web/grid-vue/features/responsive';
</script>

<template>
  <TbwGrid :rows="employees" responsive>
    <TbwGridColumn field="name" header="Name" />
    <TbwGridColumn field="department" header="Department" />

    <TbwGridResponsiveCard v-slot="{ row }: ResponsiveCardContext<Employee>">
      <div class="employee-card">
        <h3>{{ row.name }}</h3>
        <p>{{ row.department }}</p>
      </div>
    </TbwGridResponsiveCard>
  </TbwGrid>
</template>

TypeScript Support

Full generic support for row types:

<script setup lang="ts" generic="T extends Employee">
import { TbwGrid, TbwGridColumn } from '@toolbox-web/grid-vue';

interface Employee {
  id: number;
  name: string;
  salary: number;
}

const props = defineProps<{
  employees: T[];
}>();
</script>

<template>
  <TbwGrid :rows="props.employees">
    <TbwGridColumn field="name" header="Name" />
    <TbwGridColumn field="salary" header="Salary">
      <template #cell="{ value }"> ${{ value.toLocaleString() }} </template>
    </TbwGridColumn>
  </TbwGrid>
</template>

API Reference

Components

Component Description
TbwGrid Main grid wrapper component
TbwGridColumn Declarative column definition
TbwGridDetailPanel Master-detail row content
TbwGridToolPanel Custom sidebar panel
TbwGridHeaderContent Inject Vue content into the shell header zone
TbwGridToolbarContent Inject Vue content into the shell toolbar zone
TbwGridToolButtons Toolbar button container
TbwGridResponsiveCard Mobile card layout template
GridTypeProvider App-wide type defaults
GridIconProvider App-wide icon overrides
GridProvider Combined type + icon provider

Composables

Composable Description
useGrid Access grid methods

Types

Type Description
CellSlotProps Props for #cell slot
EditorSlotProps Props for #editor slot
DetailPanelContext Props for detail panel slot
ToolPanelContext Props for tool panel slot
ResponsiveCardContext Props for responsive card slot
GridConfig Grid configuration type
ColumnConfig Column configuration type
CellRenderer Cell renderer type
CellEditor Cell editor type
TypeDefault Type default configuration
TypeDefaultsMap Type defaults registry type

Requirements

  • Vue 3.4+ (uses defineModel and improved generics)
  • @toolbox-web/grid (peer dependency)

Demo

See the Vue demo app for a full-featured example using @toolbox-web/grid-vue.

bun nx serve demo-vue

Building

bun nx build grid-vue

Running Tests

bun nx test grid-vue

License

MIT © Øystein Amundsen

Related Packages

@vue/web-component-wrapper

wrap a vue component as a web component.

26,969,355
@telia/vue-web-component-wrapper-ie11

wrap a vue component as a web component, for IE 11 too.

4,272 19
prot-table

A customizable table component in vue. Intended to be used as a web-component.

2,236 5
@tipolox/litgrid-vue

Vue 3 wrapper for LitGrid

728 4
vue-qr-reader

A web component written in vue to scan QR

24,195

Version History

Version Released Vue License
2.6.3 >=3.3.0 MIT
2.6.2 >=3.3.0 MIT
2.6.1 >=3.3.0 MIT
2.6.0 >=3.3.0 MIT
2.5.0 >=3.3.0 MIT
Pre-releases (7)
Version Released Vue License
2.0.0-rc.1 >=3.3.0 MIT
2.0.0-rc.0 >=3.3.0 MIT
2.0.0-beta.1 >=3.3.0 MIT
2.0.0-beta >=3.3.0 MIT
1.0.0-rc.2 >=3.3.0 MIT