npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

vanilla-tables

v0.1.1

Published

Fast, framework-free data table plugin for vanilla JavaScript.

Readme

Vanilla Tables

Fast, framework-agnostic data tables in pure JavaScript.

CI npm version npm downloads GitHub Discussions

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-tables

Quick 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/styles globally 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.render and expandRow may return HTML strings.
  • Use sanitizeHtml to 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, error
  • search:change, filter:change, sort:change, page:change
  • row:expand, row:collapse, row:edit, row:action
  • column:resize, column:reorder, column:visibility
  • data: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.mjs

Then open http://127.0.0.1:4173/demo/index.html.

Scripts

  • npm run build
  • npm run test
  • npm run coverage
  • npm run test:e2e
  • npm run test:e2e:matrix
  • npm run test:e2e:perf
  • npm run stress
  • npm run bench:memory
  • npm run release:verify

Release Gate

Before publishing:

npm run build
npm run test
npm run test:e2e:perf
npm run release:verify

Publish To npm + CDN

  1. Login locally:
npm login
  1. Publish:
npm publish --access public
  1. 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.x with 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

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