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

gridtables

v0.1.0

Published

GridTables — a modern, hook-first, spreadsheet-grade React data grid. Keyboard-native editing, Excel copy/paste, undo, virtualized rows, grid/list/cards views. Zero runtime dependencies.

Downloads

164

Readme

GridTables

A modern, hook-first, spreadsheet-grade React data grid. Keyboard-native editing, Excel copy/paste, undo, optimistic saves, virtualized rows, and three view modes (Grid / List / Cards) — with zero runtime dependencies.

npm install gridtables
'use client';
import { useGridTable, GridTable } from 'gridtables';
import 'gridtables/styles.css';

function Properties({ rows }) {
  const grid = useGridTable({
    data: rows,
    columns: [
      { key: 'title', label: 'Property', pinned: true, width: 240 },
      { key: 'value', label: 'Value', type: 'money', width: 120 },
      { key: 'roi', label: 'ROI', type: 'pct', width: 100 },
      { key: 'vacant', label: 'Vacant', type: 'bool', width: 90 },
    ],
    onSaveCells: async (edits) => {
      await api.save(edits); // throw to revert the whole batch
    },
  });

  return <GridTable grid={grid} height="60vh" />;
}

One hook creates the instance; one component renders it. The grid object is the imperative handle — call grid.setSearch('…'), grid.undo(), grid.applyEdits([...]), grid.setView('cards') from anywhere. The component calling useGridTable never re-renders on grid state changes; subscribe to slices where you need them:

const selected = grid.useGridState((s) => s.selectedRows.size);

Features

  • Spreadsheet editing — click or type to edit, Enter saves + moves down, Tab moves right, Esc cancels, booleans toggle in place
  • Range selectionShift+arrows / Shift+click, Delete clears, Ctrl+D fills down
  • Excel round-tripCtrl+C copies the selection as TSV, Ctrl+V pastes a block back, types coerced per column
  • UndoCtrl+Z walks the edit history; every batch (paste/fill/clear) is one undo step and re-persists through your save handler
  • Optimistic savesonSaveCells(edits, meta) gets every change; cells show saving → saved states, throw to revert with an error state, or return per-cell errors for partial failure
  • Virtualized rows with sticky header + pinned column (pad-row technique, position: sticky keeps working)
  • Toolbar chrome — search, sort chips, column show/hide, field-set presets, density, fullscreen, keyboard-shortcut cheatsheet
  • Row selection with floating bulk-actions bar
  • Aggregation footer — Σ sum, average, count per column
  • Grid / List / Cards views from the same instance
  • Custom cells — per-column cell/editor renderers, or register whole cell types with defineCellType
  • SSR-safe ('use client' built in), React 18 & 19

Column types

Built-in: text · int · number · money · area · pct · bool · enum · tags · date · readonly · images · computed

{
  key: 'status', label: 'Status', type: 'enum',
  options: [{ label: 'Rented', value: 'rented' }, { label: 'Vacant', value: 'vacant' }],
  cell: ({ value }) => <span className="gt-pill" data-tone="g">{value}</span>, // custom display
}

Custom cell types:

import { defineCellType } from 'gridtables';

const rating = defineCellType({
  name: 'rating',
  align: 'center',
  cell: ({ value }) => '★'.repeat(Number(value) || 0),
  editor: (ctx) => <MyRatingEditor ctx={ctx} />, // ctx.commit(v, 'down') / ctx.cancel()
  parse: (raw) => Math.min(5, Number(raw) || 0),  // clipboard coercion
});

useGridTable({ cellTypes: { rating }, ... });

Save pipeline

Everything funnels through one callback — single edits, paste, fill-down, clear, bulk actions, undo:

onSaveCells: async (edits, meta) => {
  // meta.source: 'edit' | 'paste' | 'fill' | 'clear' | 'undo' | 'api'
  const results = await Promise.allSettled(edits.map(saveOne));
  return results.map((r, i) =>
    r.status === 'rejected'
      ? { rowId: edits[i].rowId, colKey: edits[i].colKey, error: String(r.reason) }
      : { rowId: edits[i].rowId, colKey: edits[i].colKey }
  );
}

data stays controlled by you (SWR/React Query friendly). Edits live in an optimistic overlay; when your refreshed data prop contains the saved values, the overlay prunes itself.

Theming

All styling is plain CSS driven by --gt-* custom properties on .gt-root:

.my-theme.gt-root {
  --gt-accent: #7c3aed;
  --gt-font: 'Inter', sans-serif;
  --gt-radius: 8px;
  --gt-surface: #111418; /* dark mode: override the neutral scale too */
}

State hooks for CSS: [data-selected], [data-active], [data-status="saving|saved|error"], [data-pinned], [data-sorted="asc|desc"], [data-density="compact|roomy"].

Next.js

Works out of the box in the App Router — the package ships with 'use client' banners, so you can pass grid around Client Components freely.

import 'gridtables/styles.css'; // e.g. in app/layout.tsx or the page's client component

If you consume the package via a file: symlink during development, add transpilePackages: ['gridtables'] to next.config and make sure only one React copy resolves (installing the packed .tgz avoids this class of problem entirely).

Keyboard reference

| Action | Keys | | --- | --- | | Move between cells | | | Start editing | Enter / F2 / just type | | Save, move down / right | Enter / Tab | | Cancel edit | Esc | | Select a range | Shift+arrows, Shift+click | | Select the whole page | Ctrl/⌘ A | | Copy / paste (Excel) | Ctrl/⌘ C / Ctrl/⌘ V | | Fill range down | Ctrl/⌘ D | | Clear cells | Delete | | Undo | Ctrl/⌘ Z |

Development

npm install
npm run dev        # Vite playground on :5199
npm test           # vitest (core engine)
npm run typecheck
npm run build      # tsup → dist (esm + cjs + d.ts + css)

License

MIT