UI Components > @ioi-dev/vue-table
Performance-first Vue 3 datatable with virtualization, selection, editing, and CSV export.
IOI Vue Table
A performance-first Vue 3 data table with virtual scrolling, rich filtering, row grouping, inline editing, CSV export, and server-side mode — all in a zero-dependency, TypeScript-first package.
Features
- Virtual Scroll — render 100,000+ rows at 60fps with windowed virtualisation
- Sort & Filter — multi-column sort, text / select / number / date header filters, global search
- Row Grouping — group by any field with sum, avg, count, min, max aggregations per group
- Inline Editing — click-to-edit cells with per-column validation and commit/cancel lifecycle
- Column Control — pin, reorder, resize, and toggle column visibility
- CSV Export — export all, filtered, or selected rows; formula-injection safe
- Server-Side Mode — plug in a
serverOptions.fetchfunction; the table handles paging, sorting, and filter state - Headless-Capable — import from
/unstyledand apply Tailwind, Bootstrap, or any CSS - TypeScript-First — full type definitions with generic row types
Installation
npm install @ioi-dev/vue-table
Import the default styled build:
import { Table } from '@ioi-dev/vue-table';
Or the zero-CSS unstyled build:
import { Table } from '@ioi-dev/vue-table/unstyled';
Note:
IoiTableremains available as a backward-compatible alias toTablein v1.x releases.
Quick Start
<script setup lang="ts">
import { Table, type ColumnDef } from '@ioi-dev/vue-table';
interface Employee {
id: number;
name: string;
department: string;
salary: number;
}
const columns: ColumnDef<Employee>[] = [
{ field: 'id', header: 'ID', type: 'number', width: 72 },
{ field: 'name', header: 'Name', type: 'text', width: 200, headerFilter: 'text' },
{ field: 'department', header: 'Department', type: 'text', width: 150, headerFilter: 'select' },
{ field: 'salary', header: 'Salary (£)', type: 'number', width: 130 },
];
const rows: Employee[] = [
{ id: 1, name: 'Oliver Smith', department: 'Engineering', salary: 85000 },
{ id: 2, name: 'Amelia Jones', department: 'Product', salary: 92000 },
{ id: 3, name: 'Harry Williams', department: 'Design', salary: 78000 },
];
</script>
<template>
<Table :rows="rows" :columns="columns" row-key="id" :height="320" />
</template>
Column Definition
| Property | Type | Description |
|---|---|---|
field |
string |
Key on the row object |
header |
string |
Column header label |
type |
'text' | 'number' | 'date' |
Data type for sorting and filtering |
width |
number |
Column width in pixels |
pin |
'left' | 'right' |
Pin column to an edge |
headerFilter |
'text' | 'select' |
Render a filter input in the header row |
editable |
false |
Opt a column out of inline editing |
Row Grouping
<script setup lang="ts">
import { Table, type AggregationType, type ColumnDef } from '@ioi-dev/vue-table';
const groupAggregations: Record<string, AggregationType[]> = {
salary: ['sum', 'avg', 'count'],
};
</script>
<template>
<Table
:rows="rows"
:columns="columns"
row-key="id"
group-by="department"
:group-aggregations="groupAggregations"
:height="500"
>
<template #group-header="{ group, expanded, toggle }">
<div @click="toggle()">
{{ expanded ? '▼' : '▶' }}
{{ group.value }} — {{ group.count }} rows
· Sum: £{{ group.aggregations['salary_sum'] }}
</div>
</template>
</Table>
</template>
Server-Side Mode
<script setup lang="ts">
import { ref } from 'vue';
import { Table, type ServerDataOptions } from '@ioi-dev/vue-table';
const serverOptions: ServerDataOptions<Row> = {
query: async ({ pageIndex, pageSize, sort, filters }) => {
const res = await fetch(`/api/data?page=${pageIndex}&size=${pageSize}`);
const json = await res.json();
return { rows: json.data, totalRows: json.total };
},
};
</script>
<template>
<Table
data-mode="server"
:server-options="serverOptions"
:columns="columns"
row-key="id"
:height="500"
/>
</template>
Pagination
<script setup lang="ts">
import { ref } from 'vue';
const pageIndex = ref(0);
const pageSize = ref(25);
</script>
<template>
<Table
v-model:pageIndex="pageIndex"
v-model:pageSize="pageSize"
:rows="rows"
:columns="columns"
row-key="id"
/>
</template>
Table Ref API
Expose the table ref to call imperative methods:
<script setup lang="ts">
import { ref } from 'vue';
const tableRef = ref();
// Sort
tableRef.value?.setSortState([{ field: 'salary', direction: 'desc' }]);
// Filter
tableRef.value?.setColumnFilter('department', { type: 'text', operator: 'contains', value: 'Eng' });
tableRef.value?.setGlobalSearch('Oliver');
tableRef.value?.clearAllFilters();
// Selection
tableRef.value?.selectAll('filtered');
tableRef.value?.getSelectedKeys(); // → (string | number)[]
tableRef.value?.clearSelection();
// Navigation
tableRef.value?.scrollToRow(999); // scroll to row index
tableRef.value?.setPageIndex(4);
// Export
tableRef.value?.exportCSV({ scope: 'filtered', filename: 'export.csv' });
// Groups
tableRef.value?.toggleGroupExpansion('Engineering');
tableRef.value?.expandAllGroups();
tableRef.value?.collapseAllGroups();
</script>
<template>
<Table ref="tableRef" :rows="rows" :columns="columns" row-key="id" />
</template>
Styling
The component uses a BEM class API for styling hooks:
| Class | Description |
|---|---|
.ioi-table |
Root container |
.ioi-table__viewport |
Scrollable viewport |
.ioi-table__header-content |
Header cell content wrapper |
.ioi-table__filter-row |
Header filter row (<tr>) |
.ioi-table__filter-cell |
Header filter cell (<th>) |
.ioi-table__filter-input |
Text filter input |
.ioi-table__filter-select |
Select filter dropdown |
.ioi-table__row |
Data row |
.ioi-table__row--selected |
Selected row |
.ioi-table__row--focused |
Keyboard-focused row |
.ioi-table__cell |
Data cell |
.ioi-table__cell--editing |
Actively edited cell |
.ioi-table__cell--editable |
All cells in an editable column |
.ioi-table__group-header |
Group header row |
.ioi-table__header--sorted-asc |
Ascending sort indicator |
.ioi-table__header--sorted-desc |
Descending sort indicator |
.ioi-table__header--pinned-left-edge |
Last pinned-left column header |
.ioi-table__header--pinned-right-edge |
First pinned-right column header |
.ioi-table__header--drag-over-left |
Drag target — drop before |
.ioi-table__header--drag-over-right |
Drag target — drop after |
Configuration Options
CSV Export
| Option | Default | Description |
|---|---|---|
sanitizeFormulas |
true |
Sanitises formula-like values to prevent injection attacks |
formulaEscapePrefix |
"'" |
Custom prefix for formula sanitisation ("'" or "\t") |
Performance Tuning
| Option | Default | Description |
|---|---|---|
globalSearchDebounceMs |
0 |
Debounce interval for global search (milliseconds) |
filterDebounceMs |
0 |
Debounce interval for filter operations (milliseconds) |
rowHeight |
Configurable | Row height in pixels for virtualisation calculations |
overscan |
Configurable | Extra rows rendered outside viewport for smoother scrolling |
Roadmap
| Version | Focus | Target |
|---|---|---|
| v0.2.5 | Minimal CSS tier, row reorder, clipboard copy, column groups | ✅ Released 2026-04-13 |
| v1.0 | API freeze, SemVer commitments, bundle/a11y/SSR gates, doc truth-up | 2026-Q2 |
| v1.1 | Optional Rust/WASM acceleration (sort, filter, virtual engine, CSV streaming); horizontal virtualization; dark mode | 2026-Q3 |
| v1.2 | Nested column groups, RTL, router sync, undo/redo, MCP bridge | 2026-Q4 |
| v2.0 | MCP advanced tier (multi-table, write-mode, guardrails); drop DataTable alias |
2027+ |
Core MCP bridge planned for v1.2. The WASM layer in v1.1 is fully opt-in via a separate entry point — the JavaScript implementation remains first-class with no breaking API changes. See docs/ROADMAP.md and docs/v1.0-PLAN.md for full details.
Development
npm install
# Run playground (demo site)
npm --workspace @ioi/vue-table-playground run dev
# Run tests
npm --workspace @ioi-dev/vue-table run test
# Build package
npm --workspace @ioi-dev/vue-table run build
# Lint & typecheck
npm run ci
Compatibility
| Requirement | Support |
|---|---|
| Vue 3.4+ | Full support (baseline target) |
| Vue 3.6 | Compatibility tracked in CI |
| Vapor Mode | Planned for future release (separate entry point) |
| Browsers | Modern browsers with ES2020 support |
| TypeScript | 5.x |
Contributing
Contributions are welcome. Please refer to CONTRIBUTING.md for submission requirements, coding standards, and development setup.
License
MIT