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

tablecrafter

v2.1.0

Published

Zero-dependency JavaScript data table with inline editing, advanced filtering, and mobile card view. Companion library to the TableCrafter WordPress plugin.

Readme

TableCrafter.js

The zero-dependency JavaScript data table that turns any array, API, or CSV into an editable, filterable, mobile-ready table.

npm version License: MIT ~27 KB min+gz

import TableCrafter from 'tablecrafter';

const table = new TableCrafter('#my-table', {
  data: '/api/employees',
  columns: [{ field: 'name', label: 'Name', editable: true }],
  editable: true,
});
table.render();

Why TableCrafter.js

  • No build step required. Drop the UMD script on any page and you are done. No bundler, no framework, no fuss.
  • Spreadsheet-grade editing in the browser. 14 built-in cell editors, 15+ validation rules, lookup dropdowns, formula columns, and a custom cell-type registry -- all without a server round-trip per keypress.
  • Mobile cards, not squished columns. The responsive card layout collapses rows into readable cards at configurable breakpoints instead of making users scroll sideways.
  • Your data stays yours. No SaaS, no telemetry, no external requests unless you point it at your own API. MIT licensed.

Feature grid

Display

| Feature | Details | |---|---| | Multi-column sort | Unlimited sort keys, shift-click, badges, custom comparators | | Per-column filters | Text, multiselect, date range, number range; type auto-detection | | Advanced search grammar | AND / OR / negation / field:value / regex / comparison operators | | Mobile card view | Responsive breakpoints, expandable sections, field-level visibility | | Virtual scroll | Windowed rendering via enableVirtualScroll() -- no pagination required | | Formula columns | Arithmetic, comparisons, IF, CONCAT, LENGTH, UPPER, LOWER | | Conditional formatting | Data bars, color scales, icon sets; ARIA labels on visual-only cues | | Heatmap cells | Inline SVG heatmap from an array-of-numbers (cellType: 'heatmap') | | Cell renderers | Badge, link, progress bar, sparkline built-in | | Cell range selection | Click-drag range with TSV clipboard copy | | Right-click context menu | ARIA-compliant, fully configurable items; keyboard navigation | | Column management | Programmatic reorder, show/hide, pin left/right | | RTL support | Locale-driven layout flip (dir="rtl" + tc-rtl class) | | i18n | 6 bundled locales: en, es, fr, de, ar, ur; custom number/date formatters |

Editing

| Feature | Details | |---|---| | 14 inline cell editors | text, textarea, number, email, date, datetime, select, multiselect, checkbox, radio, file, url, color, range | | Custom cell type registry | registerCellType() for any editor | | 15+ validation rules | required, minLength, maxLength, min, max, pattern, email, url, phone, date bounds, oneOf, notOneOf, unique, custom function | | Add row modal | Built-in creation form with full validation | | Bulk operations | Multi-row select, delete, export, custom actions | | Role-based permissions | Per-action (view/edit/delete/create), row-level ownOnly ownership |

Data

| Feature | Details | |---|---| | Inline data | Plain JavaScript array | | REST API | Fetch from any URL; custom auth headers; root path; CRUD write-back | | CSV export | RFC-4180, filtered, injection-safe | | JSON export | Serialized current dataset | | XLSX / PDF export | Requires optional peer deps -- see Installation | | State persistence | localStorage / sessionStorage; saves filters, sort, page | | Plugin system | use(plugin, opts) / unuse(name) with full lifecycle hooks | | Events API | on / off / once for 8 named events |

Platform

| Feature | Details | |---|---| | Zero runtime dependencies | Pure vanilla JavaScript | | ESM + CJS + UMD builds | Works with webpack, Vite, Rollup, or a plain <script> tag | | CDN auto-init | TableCrafter.bootstrap() from data-tc-bootstrap attributes | | TypeScript definitions | Full .d.ts included (src/tablecrafter.d.ts) | | Framework-agnostic | React, Vue, Svelte, Angular, or plain HTML | | jsDelivr | https://cdn.jsdelivr.net/npm/tablecrafter@2/dist/tablecrafter.umd.min.js |


Installation

CDN (no build step)

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/tablecrafter@2/dist/tablecrafter.css">
<script src="https://cdn.jsdelivr.net/npm/tablecrafter@2/dist/tablecrafter.umd.min.js"></script>

The global is TableCrafter.

npm

npm install tablecrafter
import TableCrafter from 'tablecrafter';
import 'tablecrafter/dist/tablecrafter.css';

Optional peer dependencies (XLSX / PDF export)

npm install xlsx                    # XLSX export
npm install jspdf jspdf-autotable   # PDF export

Quick start

<div id="my-table"></div>

<script src="https://cdn.jsdelivr.net/npm/tablecrafter@2/dist/tablecrafter.umd.min.js"></script>
<script>
const table = new TableCrafter('#my-table', {
  data: [
    { id: 1, name: 'Alice', role: 'Engineer' },
    { id: 2, name: 'Bob',   role: 'Designer' },
  ],
  columns: [
    { field: 'id',   label: 'ID' },
    { field: 'name', label: 'Name', editable: true },
    { field: 'role', label: 'Role', editable: true },
  ],
  editable: true,
  filterable: true,
  pagination: true,
});
table.render();
</script>

CDN auto-init (no JavaScript required)

Any element with a data-tc-bootstrap attribute is instantiated automatically when TableCrafter.bootstrap() runs. The configuration comes from the element's data-tc-config JSON attribute:

<div data-tc-bootstrap
     data-tc-config='{"columns":[{"field":"name","label":"Name"}],
                      "data":[{"name":"Alice"},{"name":"Bob"}],
                      "filterable":true}'></div>

<script src="https://cdn.jsdelivr.net/npm/tablecrafter@2/dist/tablecrafter.umd.min.js"></script>
<script>
const tables = TableCrafter.bootstrap();       // Map<HTMLElement, TableCrafter>
// TableCrafter.bootstrap('#my-section');      // scope to a subtree
</script>

Live examples

The examples/ directory contains runnable HTML files. Start any static server from the repo root:

python -m http.server 8000
# then open http://localhost:8000/examples/advanced-features.html

WordPress?

TableCrafter.js shares feature DNA with the TableCrafter WordPress plugin. If you need server-side data sources, Gravity Forms write-back, Gutenberg blocks, or WooCommerce integration, the plugin handles those. For the current gap status between the two, see docs/PARITY.md.

Visit tablecrafter.com for the plugin.


Configuration reference

Constructor: new TableCrafter(container, config) where container is a CSS selector string or HTMLElement.

Core options

| Key | Type | Default | Description | |---|---|---|---| | data | array \| string | [] | Row data or URL to fetch | | columns | array | [] | Column definitions (required) | | editable | boolean | false | Enable inline editing globally | | sortable | boolean | true | Enable column sorting | | filterable | boolean | true | Enable per-column filters | | globalSearch | boolean | true | Enable the search grammar bar | | pagination | boolean | false | Enable pagination | | pageSize | number | 25 | Rows per page | | exportable | boolean | false | Show CSV export button | | exportFilename | string | 'table-export.csv' | Default download filename |

Column definition

{
  field: 'email',       // data key (required)
  label: 'Email',       // header text
  type: 'email',        // cell renderer: text|badge|link|progress|sparkline|...
  editable: true,       // enable this column for editing
  sortable: true,
  filterable: true,
  formula: 'price * qty', // computed column expression
  aggregate: 'sum',     // sum | count | avg | min | max | distinct
  cellType: 'heatmap',  // built-in cell type override
  lookup: {
    url: '/api/users',
    valueField: 'id',
    labelField: 'name',
  },
}

Editing and validation example

const table = new TableCrafter('#table', {
  data: rows,
  columns: [
    { field: 'email', label: 'Email', editable: true },
    { field: 'age',   label: 'Age',   editable: true },
  ],
  validation: {
    rules: {
      email: [{ type: 'required' }, { type: 'email' }],
      age:   [{ type: 'min', value: 18 }, { type: 'max', value: 120 }],
    },
  },
  onEdit({ row, field, value }) {
    console.log(`${field} changed to`, value);
  },
});
table.render();

Permissions example

const table = new TableCrafter('#table', {
  data: rows,
  columns,
  permissions: {
    enabled: true,
    edit:   ['admin', 'manager'],
    delete: ['admin'],
    ownOnly: true,   // users see only rows they own
  },
});

table.setCurrentUser({ id: 42, roles: ['manager'], username: 'alice' });
table.render();

i18n

const table = new TableCrafter('#table', {
  data: rows,
  columns,
  i18n: {
    locale: 'ar',           // Arabic -- triggers RTL layout automatically
    fallbackLocale: 'en',
    messages: {
      ar: { 'search.placeholder': 'ابحث...' },
    },
  },
});

Six locales are bundled: en, es, fr, de, ar, ur. RTL is applied automatically for ar and ur.

Custom number and date formatters

Supply i18n.formats to override how number and date columns render:

i18n: {
  locale: 'de',
  formats: {
    // Intl.NumberFormat options object, or a function(value, locale) => string
    formatNumber: { style: 'currency', currency: 'EUR' },
    // Function(value, locale) => string
    formatDate: (value, locale) =>
      new Intl.DateTimeFormat(locale, { dateStyle: 'medium' }).format(new Date(value)),
  },
},

Runtime locale switching

table.setLocale('fr');

// Register or override message keys for any locale
table.addMessages('es', { 'toolbar.search': 'Buscar...' });

Plugin system

const myPlugin = {
  name: 'my-plugin',
  install(table, opts) {
    // extend or monkey-patch `table` here
    console.log('installed with', opts);
  },
};

const table = new TableCrafter('#table', {
  data: rows,
  columns,
  plugins: [[myPlugin, { debug: true }]],
});

Lifecycle hooks

Plugins declare hooks inside a hooks object. Return false from any before* hook to cancel the operation. Available pairs: beforeRender/afterRender, beforeSort/afterSort, beforeEdit/afterEdit (payloads: { rowIndex, field, value } / { rowIndex, field, oldValue, newValue }), beforeLoad/afterLoad (payloads: { source } / { data }), and destroy.

const guardPlugin = {
  name: 'guard',
  hooks: {
    beforeEdit: ({ field, value }) => {
      if (field === 'salary' && value < 0) return false; // cancel
    },
    afterEdit: ({ field, oldValue, newValue }) => {
      console.log(`${field}: ${oldValue} -> ${newValue}`);
    },
    destroy: () => console.log('teardown'),
  },
};

Events

The events API lets you observe table activity from outside the config callbacks. on() and once() both return an unsubscribe function.

// Persistent subscription
const unsub = table.on('cellEdit', ({ row, field, oldValue, newValue }) => {
  console.log(`Row ${row}: ${field} changed from ${oldValue} to ${newValue}`);
});

// One-shot subscription (auto-removes after the first firing)
table.once('rowAdd', ({ row, index }) => {
  console.log('First row added at index', index, ':', row);
});

// Unsubscribe via the returned function
unsub();

// Or by reference
const handler = ({ page }) => console.log('page', page);
table.on('pageChange', handler);
table.off('pageChange', handler);

Event reference

| Event | Payload | Fired by | |---|---|---| | cellEdit | { row, field, oldValue, newValue } | saveEdit() / cell commit | | selectionChange | { selectedRows } | toggleRowSelection() | | sort | { sortKeys } | sort() / multiSort() | | filter | { filters } | setFilter() / clearFilters() | | pageChange | { page } | goToPage() / nextPage() / prevPage() | | rowAdd | { row, index } | addRow() | | rowUpdate | { row, index, previous } | updateRow() | | rowDelete | { row, index } | removeRow() |

Config callbacks (onEdit, onSort, etc.) still fire alongside events. A throwing handler is caught and logged; other handlers in the same event still run. See examples/events-and-hooks.html.


TypeScript

The package ships src/tablecrafter.d.ts. The types field in package.json points to it automatically.

import TableCrafter, { TableCrafterConfig, TableCrafterColumn } from 'tablecrafter';

const columns: TableCrafterColumn[] = [
  { field: 'id', label: 'ID' },
  { field: 'name', label: 'Name', editable: true },
];

const table = new TableCrafter('#table', { data: [], columns });
table.render();

Framework integration

React

import { useEffect, useRef } from 'react';
import TableCrafter from 'tablecrafter';

export function DataTable({ data, onEdit }) {
  const ref = useRef(null);
  useEffect(() => {
    const table = new TableCrafter(ref.current, { data, onEdit, columns: [] });
    table.render();
    return () => table.destroy();
  }, [data]);
  return <div ref={ref} />;
}

Export

CSV and JSON export work out of the box:

table.downloadCSV();           // triggers browser download
const json = table.exportToJSON();

XLSX and PDF export require optional peer dependencies (install commands above under Installation).


API methods (summary)

| Method | Description | |---|---| | render() | Render or re-render the table | | destroy() | Tear down and remove listeners | | setData(rows) | Replace all data | | getData() | Current (unfiltered) data | | getFilteredData() | Data after search/filter applied | | addRow(row) | Append a row | | updateRow(i, updates) | Patch a row by index | | removeRow(i) | Delete a row by index | | setFilter(field, value) | Set a column filter | | clearFilters() | Reset all filters | | sort(field, opts?) | Sort by field | | multiSort(keys) | Sort by multiple fields | | goToPage(n) | Jump to page | | exportToCSV() | Return CSV string | | exportToJSON() | Return JSON string | | downloadCSV() | Trigger browser CSV download | | setCurrentUser(user) | Set user context for permissions | | hasPermission(action, row?) | Check permission | | use(plugin, opts?) | Register a plugin | | unuse(name) | Remove a plugin by name | | setLocale(locale) | Switch i18n locale at runtime | | addMessages(locale, messages) | Register or override i18n strings | | on(event, handler) | Subscribe to a named event; returns unsub function | | off(event, handler) | Unsubscribe a handler | | once(event, handler) | Subscribe for one emission; returns unsub function | | enableVirtualScroll(opts?) | Enable windowed rendering (rowHeight, viewportHeight, overscan) | | disableVirtualScroll() | Disable virtual scroll | | isVirtualScrolling() | Returns true when virtual scroll is active | | getAggregates() | Aggregated column values (sum / count / avg / min / max / distinct) | | saveState() | Persist current state | | loadState() | Restore persisted state | | snapshotHTML(opts?) | Deterministic HTML snapshot for testing (scope: 'table'|'wrapper') | | TableCrafter.bootstrap(scope?) | Auto-init from [data-tc-bootstrap] elements; returns Map | | TableCrafter.getBrowserSupport() | Capability probe returning { intl, resizeObserver, requiredFeaturesAvailable, ... } | | TableCrafter.minimumBrowserSupportNotice() | Human-readable string listing minimum requirements |


Testing

npm test           # run Jest suite
npm run test:watch
npm run test:coverage

Contributing

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/description
  3. Add tests for new functionality
  4. Ensure all tests pass: npm test
  5. Open a pull request against main

Roadmap: parity gaps and planned work are tracked in Epic #323 and the parity matrix.


License

MIT -- see LICENSE.