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

react-sheet-grid

v0.1.0

Published

A Google Sheets-style spreadsheet grid for React: canvas rendering, one million rows at 60fps, formulas, undo/redo, sorting, filtering and Vietnamese IME support.

Readme

react-sheet-grid

A spreadsheet grid for React with the feel of Google Sheets. The grid is drawn on a canvas, so a sheet of one million rows by a hundred columns scrolls at 60 fps; editing, menus and the toolbar are ordinary DOM on top of it.

  • Formulas with relative and absolute references, a dependency graph and incremental recalculation
  • Undo/redo, copy/cut/paste (Excel and Google Sheets compatible), fill handle, sort, filter, insert/delete, hide, freeze, zoom
  • Vietnamese (Telex/VNI) and other IME input works while typing in a cell
  • Toolbar, formula bar and status bar included (and usable on their own)
  • English and Vietnamese UI, light and dark theme
  • No runtime dependencies besides React

Install

npm install react-sheet-grid
# react and react-dom (18 or newer) are peer dependencies

Quick start

import { SheetGrid } from 'react-sheet-grid';

export function App() {
  return (
    <div style={{ height: 600 }}>
      <SheetGrid rowCount={1000} colCount={26} />
    </div>
  );
}

SheetGrid fills its parent (it needs a parent with a height). It renders the toolbar, the formula bar, the grid and the status bar.

Saving and loading

import { SheetGrid, type SheetGridHandle } from 'react-sheet-grid';

function Editor({ saved }: { saved?: unknown }) {
  const ref = useRef<SheetGridHandle>(null);
  return (
    <SheetGrid
      ref={ref}
      defaultValue={saved}                       // a snapshot from a previous session
      onChange={({ getSnapshot }) => {
        localStorage.setItem('sheet', JSON.stringify(getSnapshot()));   // or send it to your server
      }}
    />
  );
}

A snapshot is plain JSON: cells (values and formulas as text), formatting, sizes, sort/filter, frozen panes. Formulas are recalculated when it is loaded. The undo history and the selection are not saved.

onChange fires after the document changed (edits, paste, formatting, undo, freezing), never for selection or scrolling and never while readOnly is on. Calls are batched (changeDelay, 300 ms by default) and one more call is made on unmount if changes are still pending. getSnapshot() walks every filled cell, so call it when you save, not on every render.

Props

| Prop | Type | Description | |---|---|---| | defaultValue | unknown | A saved snapshot. Read once at mount; use ref.current.load(snapshot) to replace the sheet later. A snapshot that cannot be read starts a blank sheet and calls onError. | | rowCount, colCount | number | Size of a new sheet (default 1000 × 26). Users can change them from the toolbar. | | readOnly | boolean | Blocks every change to cells, formatting and structure. Sorting, filtering, hiding, freezing, zooming and copying still work. | | locale | 'en' \| 'vi' | UI language (default 'en'). | | messages | Partial<Messages> | Overrides single UI strings. | | theme | 'light' \| 'dark' \| 'auto' | Default 'light'; 'auto' follows the operating system. | | toolbar, formulaBar, statusBar | boolean | Show or hide each bar (all true). | | frozenRows, frozenCols | number | Initial frozen panes. | | zoom, onZoomChange | number, (zoom) => void | Zoom factor from 0.5 to 2. | | onChange, changeDelay | | See above. | | onError | (error: Error) => void | Called when defaultValue is unreadable. | | className, style | | Applied to the outer box. |

The ref

interface SheetGridHandle {
  sheet: Spreadsheet;                 // the headless model: read cells, run commands, subscribe
  controller: GridController | null;  // input and rendering controller (null until mounted)
  getSnapshot(): SheetSnapshot;
  load(snapshot: unknown): void;      // throws SnapshotError for bad data and leaves the sheet untouched
  focus(): void;
}

Building your own layout

SheetGrid is a composition of exported parts. Use them directly when you want a different arrangement:

import { useState } from 'react';
import { DataGrid, FormulaBar, GridProvider, Spreadsheet, StatusBar, Toolbar, type GridController } from 'react-sheet-grid';

const sheet = new Spreadsheet({ rowCount: 100, colCount: 26 });

function Custom() {
  const [grid, setGrid] = useState<GridController | null>(null);
  return (
    <GridProvider locale="vi" theme="auto">
      <div style={{ height: 500, display: 'flex', flexDirection: 'column' }}>
        <Toolbar sheet={sheet} grid={grid} onAction={() => grid?.editor.focus()} />
        <FormulaBar sheet={sheet} grid={grid} />
        <div style={{ flex: 1, minHeight: 0 }}>
          <DataGrid sheet={sheet} onReady={setGrid} />
        </div>
        <StatusBar sheet={sheet} />
      </div>
    </GridProvider>
  );
}

The Spreadsheet class has no dependency on React or the DOM; it also runs in Node (useful for tests and for server-side processing):

import { Spreadsheet, serializeSheet, deserializeSheet } from 'react-sheet-grid';

const sheet = new Spreadsheet({ rowCount: 100, colCount: 10 });
sheet.setCellInput(0, 0, '5');
sheet.setCellInput(1, 0, '=A1*2');
sheet.getCellByView(1, 0).value;          // 10
const saved = JSON.stringify(serializeSheet(sheet));
const again = deserializeSheet(JSON.parse(saved));

Every change goes through sheet.execute(command), which is what makes undo/redo work; setCellInput, formatSelection, insertRows, … are conveniences that do that for you. sheet.subscribe(fn) fires on every change including selection; sheet.subscribeChanges(fn) only when the document changed.

Formulas

References: A1, $A$1, A1:B5, A:A, 3:3. Arguments may be separated by , or ;.

SUM AVERAGE COUNT COUNTA MIN MAX ROUND ABS INT SQRT MOD POWER · IF IFERROR AND OR NOT · CONCAT LEN UPPER LOWER TRIM LEFT RIGHT MID · SUMIF COUNTIF · VLOOKUP INDEX MATCH

Errors follow Google Sheets: #DIV/0!, #VALUE!, #REF!, #N/A, #NAME?, #NUM!, #ERROR!. A circular reference makes the cells on the cycle #REF!.

Keyboard

Press Ctrl/⌘+/ in the grid for the full list. The usual ones work: arrows, Tab/Enter, F2, Ctrl+arrows, Shift+arrows, Ctrl+Z/Y, Ctrl+C/X/V (Ctrl+Shift+V values only), Ctrl+D/R fill, Ctrl+B/I/U, F4 for $.

Next.js and server rendering

The package is marked 'use client'. In the App Router import it from a Client Component or from a file that starts with 'use client'. In the Pages Router, load it with next/dynamic and { ssr: false } if you want to avoid server-rendering an empty box. The grid needs a browser to draw (canvas), so nothing is drawn on the server.

Browser support

Current Chrome, Edge, Firefox and Safari. The test suite runs on Chromium, Firefox and WebKit.

Limits to know about

  • Dates are not a separate type yet (no date series in the fill handle, no date formats).
  • No merged cells, multiple sheets, charts, conditional formatting or data validation.
  • Formulas refer to cells by their data position, so when the view is sorted the A1 labels do not follow the displayed order.
  • The canvas theme is page-wide: two grids on one page cannot use different themes.
  • Cut and copy include rows hidden with "Hide rows".

Development

pnpm install
pnpm dev          # demo at http://localhost:5173/demo/
pnpm test         # unit tests (Vitest)
pnpm test:e2e     # Playwright on Chromium, Firefox and WebKit
pnpm typecheck && pnpm lint
pnpm build        # builds dist/ (ESM + CJS + types)

License

MIT