vanilla-tables
v0.1.1
Published
Fast, framework-free data table plugin for vanilla JavaScript.
Maintainers
Readme
Vanilla Tables
Fast, framework-agnostic data tables in pure JavaScript.
Vanilla Tables is an object-oriented table engine designed for real apps: rich features, extensible API, production-safe behavior, and strong test coverage.
Why Vanilla Tables
- No framework lock-in: works with plain HTML, React, Vue, Angular, Svelte, or any SSR/CSR stack.
- Feature-complete table core: search, filters, sorting, paging, fixed regions, row expansion/editing/actions.
- Built for production: worker projection with timeout/retry/fallback, resilient state flow, and release verification gates.
- Theme-ready: raw semantic output by default, plus framework-focused theme plugins.
Install
npm install vanilla-tablesQuick Start (npm)
import { createVanillaTable, bootstrapThemePlugin } from 'vanilla-tables';
import 'vanilla-tables/styles';
const rows = [
{ id: 1, name: 'Alice', city: 'NYC', score: 45 },
{ id: 2, name: 'Bob', city: 'Paris', score: 20 }
];
const table = createVanillaTable(document.querySelector('#table-root'), rows, {
pageSize: 10,
searchable: true,
columnFilters: true,
multiSort: true,
rowActions: [
{
id: 'approve',
label: 'Approve',
onClick: ({ row }) => console.log('approve', row)
}
]
});
table.use(bootstrapThemePlugin());Quick Start (CDN)
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vanilla-tables/dist/vanilla-tables.css" />
<script src="https://cdn.jsdelivr.net/npm/vanilla-tables/dist/vanilla-tables.min.js"></script>
<script>
const table = new VanillaTables.VanillaTable(document.querySelector('#table-root'), data, {
fixedHeader: true,
fixedFooter: true,
fixedColumns: 1
}).init();
</script>Framework Wrappers
Vanilla Tables is framework-agnostic. Use these minimal wrappers to integrate with your preferred stack.
Wrapper behavior note:
These minimal wrappers treat options as initialization-time configuration.
If options can change at runtime in your app, re-create the table instance when options change.
React
import React, { useEffect, useRef } from 'react';
import { createVanillaTable } from 'vanilla-tables';
import 'vanilla-tables/styles';
const VanillaTableWrapper = ({ data, options }) => {
const tableRef = useRef(null);
const instanceRef = useRef(null);
useEffect(() => {
// Mount
if (tableRef.current && !instanceRef.current) {
instanceRef.current = createVanillaTable(tableRef.current, data, options);
}
// Destroy
return () => {
if (instanceRef.current) {
instanceRef.current.destroy();
instanceRef.current = null;
}
};
}, []);
// Update
useEffect(() => {
if (instanceRef.current) {
instanceRef.current.setData(data);
}
}, [data]);
return <div ref={tableRef} />;
};
export default VanillaTableWrapper;Vue 3 (Composition API)
<template>
<div ref="tableRoot"></div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, watch } from 'vue';
import { createVanillaTable } from 'vanilla-tables';
import 'vanilla-tables/styles';
const props = defineProps({
data: { type: Array, required: true },
options: { type: Object, default: () => ({}) }
});
const tableRoot = ref(null);
let tableInstance = null;
onMounted(() => {
// Mount
tableInstance = createVanillaTable(tableRoot.value, props.data, props.options);
});
onUnmounted(() => {
// Destroy
if (tableInstance) {
tableInstance.destroy();
}
});
// Update
watch(
() => props.data,
(newData) => {
if (tableInstance) {
tableInstance.setData(newData);
}
},
{ deep: true }
);
</script>Angular
import { Component, ElementRef, Input, OnChanges, OnDestroy, AfterViewInit, ViewChild, SimpleChanges } from '@angular/core';
import { createVanillaTable } from 'vanilla-tables';
@Component({
selector: 'app-vanilla-table',
template: '<div #tableRoot></div>',
standalone: true
})
export class VanillaTableComponent implements AfterViewInit, OnChanges, OnDestroy {
@ViewChild('tableRoot') tableRoot!: ElementRef;
@Input() data: any[] = [];
@Input() options: any = {};
private tableInstance?: any;
ngAfterViewInit() {
// Mount
this.tableInstance = createVanillaTable(this.tableRoot.nativeElement, this.data, this.options);
}
ngOnChanges(changes: SimpleChanges) {
// Update
if (this.tableInstance && changes['data']) {
this.tableInstance.setData(this.data);
}
}
ngOnDestroy() {
// Destroy
if (this.tableInstance) {
this.tableInstance.destroy();
}
}
}Troubleshooting Notes
- React Strict Mode: In development, React 18+ intentionally mounts, unmounts, and remounts components once to detect side effects. The cleanup in each wrapper keeps this behavior safe.
- Style Imports: Ensure you import
vanilla-tables/stylesglobally or within the component to apply base table styling. - SSR: Vanilla Tables requires a DOM environment. If using Next.js or Nuxt, wrap the component in a client-only check or use dynamic imports.
Core Features
- Global search
- Per-column filters
- Single and multi-sort
- Paging with configurable sizes
- Expandable rows
- Editable rows/cells
- Row actions and action dropdown plugin
- Fixed header/footer/columns/top rows
- Column resize and reorder
- Virtual scrolling
- URL + local persistence sync
- Server-side mode (REST/GraphQL/cursor adapters)
- Event system across all major interactions
- i18n via configurable labels
Performance + Reliability
- Adaptive worker projection engine for heavy query workloads.
- Worker controls:
threshold,workers,timeoutMs,retries. - Graceful degradation to sync path if workers fail or timeout.
- Stress + e2e + unit coverage pipeline.
- Release verification script for dist artifacts, exports, size budgets, and tarball contents.
Baseline Snapshot (2026-02-19)
Measured via npm run bench on local maintainer hardware using current defaults.
| rows | render (ms) | refresh-hit (ms) | search (ms) | sort (ms) | status | | ------ | ----------- | ---------------- | ----------- | --------- | ---------------------------- | | 10000 | 14 | 3 | 16 | 6 | OK | | 25000 | 47 | 3 | 40 | 7 | OK | | 50000 | 59 | 2 | 47 | 12 | OK | | 100000 | 104 | 2 | 77 | 15 | STOP (render > 100ms target) |
Methodology and reproduction commands: docs/benchmark-methodology.md
Strict Snapshot (2026-02-27)
Measured via npm run stress:strict after updating the stress harness to exclude fixture generation from render_ms.
| rows | render (ms) | refresh-hit (ms) | search (ms) | sort (ms) | status | | ------ | ----------- | ---------------- | ----------- | --------- | ----------------------------- | | 25000 | 37 | 1 | 48 | 10 | OK | | 50000 | 47 | 1 | 60 | 21 | OK | | 100000 | 95 | 1 | 78 | 30 | OK | | 200000 | 240 | 1 | 115 | 34 | STOP (render > 100ms target) |
Current 100ms strict ceiling: 100000 rows on maintainer hardware.
Theming
Default output is semantic and framework-neutral (vt-*).
Built-in theme plugins:
bootstrapThemePlugin()bulmaThemePlugin()muiThemePlugin()tailwindThemePlugin()
Production Tuning
const table = createVanillaTable(root, rows, {
virtualScroll: {
enabled: true,
adaptiveOverscan: true
},
virtualColumns: {
enabled: true,
width: 180,
overscan: 2
},
parallel: {
enabled: true,
threshold: 20000,
workers: 'auto',
timeoutMs: 4000,
retries: 1,
typedColumns: true
}
});HTML Safety
column.renderandexpandRowmay return HTML strings.- Use
sanitizeHtmlto sanitize those strings before insertion.
const table = createVanillaTable(root, rows, {
sanitizeHtml: (html) => DOMPurify.sanitize(html)
});API Highlights
Main instance methods:
init(),refresh(),destroy()setData(rows),addRow(row),removeRowById(rowId)search(term),filterBy(key, value),clearFilters()sortBy(key, direction?, additive?),clearSort()goToPage(page),setPageSize(size)expandRow(rowId),collapseRow(rowId),toggleRow(rowId)updateCell(rowId, key, value)setColumnWidth(key, width),reorderColumns(order)setColumnVisibility(key, visible),toggleColumnVisibility(key)setThemeClasses(classes)getState(),setState(payload),getView(),getRows()on(event, callback),use(plugin),registerHook(name, callback)
Events
Includes lifecycle, state, table interactions, data, and error/loading events.
Examples:
init,change,state:change,destroy,errorsearch:change,filter:change,sort:change,page:changerow:expand,row:collapse,row:edit,row:actioncolumn:resize,column:reorder,column:visibilitydata:set,data:add,data:remove
Demos
- Index:
demo/index.html - Individual pages: vanilla, bootstrap, bulma, mui-like, tailwind, react-wrapper, vue-wrapper
Run locally:
node tests/e2e/server.mjsThen open http://127.0.0.1:4173/demo/index.html.
Scripts
npm run buildnpm run testnpm run coveragenpm run test:e2enpm run test:e2e:matrixnpm run test:e2e:perfnpm run stressnpm run bench:memorynpm run release:verify
Release Gate
Before publishing:
npm run build
npm run test
npm run test:e2e:perf
npm run release:verifyPublish To npm + CDN
- Login locally:
npm login- Publish:
npm publish --access public- CDN availability:
- jsDelivr:
https://cdn.jsdelivr.net/npm/vanilla-tables/dist/vanilla-tables.min.js - unpkg:
https://unpkg.com/vanilla-tables/dist/vanilla-tables.min.js
The package already exposes unpkg and jsdelivr fields in package.json.
How To Grow Adoption
- Keep examples strong: add short recipe pages for the top 10 use-cases.
- Add comparison docs: “Vanilla Tables vs DataTables/Tabulator/AG Grid”.
- Ship copy-paste snippets for React/Vue/Angular wrappers.
- Keep release notes tight and frequent (
v0.xwith clear changelogs). - Add benchmark page with reproducible methodology and raw numbers.
- Label and respond to issues quickly to build maintainer trust.
- Add “good first issue” labels and contributor onboarding tasks.
- Publish short demo videos/gifs for key features.
Contributing
PRs are welcome. Keep changes test-backed and consistent with project principles:
- clear API surface
- performance-focused data paths
- graceful degradation behavior
- documented internals and public usage
Start here:
- Pick a task from pinned issue
#14: https://github.com/lhozdroid/vanilla-tables/issues/14 - Join roadmap discussion
#15: https://github.com/lhozdroid/vanilla-tables/discussions/15 - For questions: https://github.com/lhozdroid/vanilla-tables/discussions
Hall Of Contributors
- @Chidwan3578 - framework wrapper recipes and demos
Docs
- Architecture:
docs/architecture.md - Development workflow:
docs/development.md - Benchmark methodology:
docs/benchmark-methodology.md
Project Health
- Changelog:
CHANGELOG.md - Code of Conduct:
.github/CODE_OF_CONDUCT.md - Security policy:
.github/SECURITY.md - Support guide:
.github/SUPPORT.md
License
MIT
