ReoGrid Web is a JavaScript/TypeScript spreadsheet library that puts an Excel-grade editing experience — formula engine, xlsx I/O, canvas renderer — into a single dependency-free package. With React and Vue wrappers and a free Lite tier on npm, it is the same engine our .NET edition has been refining for over a decade, now rebuilt natively for the browser.
v1.2 is the third feature release since the official launch and rounds out the formula and editing UX to something that genuinely feels like Excel: 109 built-in functions, color-coded references inside the formula text with matching highlights on the grid, drag-to-move and drag-to-resize on those highlights, and an Excel-style fill handle. Under the hood, the canvas renderer issues roughly 70% fewer drawing-API calls per scroll frame.
This post is a tour of what is new and what changed since v1.0.
Formula library: 109 functions
The Lite tier ships an arithmetic-only formula engine. The Pro tier in v1.2 now exposes 109 built-in Excel-compatible functions across the categories below, so formulas in imported .xlsx files just work.
| Category | Examples |
|---|---|
| Lookup | VLOOKUP, HLOOKUP, INDEX, MATCH, XLOOKUP, XMATCH |
| Multi-criteria aggregation | SUMIFS, COUNTIFS, AVERAGEIFS, SUMPRODUCT |
| Stats | MEDIAN, LARGE, SMALL, RANK, CEILING, FLOOR, MROUND |
| Date | TODAY, NOW, YEAR, MONTH, DAY, WEEKDAY, EDATE, EOMONTH, DATEDIF |
| Math/Trig | EXP, LN, LOG, SIN, COS, TAN, ASIN, ACOS, ATAN, ATAN2, RAND, RANDBETWEEN |
| Text | SEARCH, EXACT, PROPER, CHAR, CODE |
| Reference | ROW, COLUMN, ROWS, COLUMNS, ADDRESS |
| Logic | IF, IFS, SWITCH, AND, OR, NOT |
Argument order matches Excel — including the gotchas. ATAN2(x, y) in Excel is the reverse of Math.atan2(y, x) in JavaScript; we follow Excel. EDATE clamps the day to the target month’s last day, so EDATE("2026-01-31", 1) correctly returns Feb 28 (or Feb 29 in a leap year).
The full function reference lives in the formula functions doc.
Excel-like formula reference editing
The single biggest UX investment in v1.2 is in the formula editor itself. Type = in a cell and:
- Every reference you type (
A1,B2:D10) is colored inline in the formula text, drawn from a shared 8-color palette so distinct refs are visually distinct. - The grid simultaneously draws a matching dashed rectangle around each referenced range. If your reference overlaps a merged region, the highlight expands to enclose the full merge.
- Clicking another cell inserts that cell’s address at the caret. Drag extends it into a range. Point mode is sticky past mouseup, so a second click re-targets the just-inserted ref — same as Excel.
- Drag the dashed border to translate a range. Drag a corner grip to resize it. Every occurrence of that reference in the formula text is rewritten in step, preserving each token’s absolute-reference (
$) flags. Resize uses cell-center boundaries — a cell only joins or leaves the range once the pointer passes its midpoint, matching Excel and our .NET edition.
This makes building formulas like =SUMIFS(D2:D100, B2:B100, "Active", C2:C100, ">100") something you can largely do by pointing rather than typing.
Auto-fill (drag fill handle)
A small square at the bottom-right of the selection now lets you drag to extend values down, up, left, or right.
- Single cell: tile-copies the value.
- Two or more numeric cells: extrapolates as an arithmetic progression (
1, 2→3, 4, 5). - A single date-formatted cell: increments by one day per step.
- Formula cells: shifts relative references the way Excel does. Absolute
$references are preserved.
Styles, number formats, and cell types propagate to the new region. Everything is undo/redo aware — Cmd/Ctrl+Z restores prior values including cells that previously held different data.
Disable it globally with ReogridOptions.autoFill: false or per-instance via worksheet.setAutoFillEnabled(false). For programmatic use, AutoFillAction and computeAutoFillValues() are exposed.
Idiomatic event props on React and Vue wrappers
Previously, hooking into worksheet events required calling worksheet.on*() imperatively after the grid was ready. v1.2 exposes them as proper React props and Vue emits:
import { Reogrid } from '@reogrid/pro/react';
<Reogrid
onReady={({ worksheet }) => worksheet.cell('A1').setValue('Hello')}
onSelectionChange={(range) => console.log('selected', range)}
onCellValueChange={({ row, column, newValue }) => save(row, column, newValue)}
onBulkCellsChange={(cells) => bulkSave(cells)}
onScrollChange={({ x, y }) => syncMinimap(x, y)}
onViewportSizeChange={(size) => recomputeLayout(size)}
onStructureChange={() => invalidateRowIndex()}
style={{ width: '100%', height: 500 }}
/>
Callbacks are held in refs, so subscriptions stay mounted across parent re-renders while always firing the freshest closure. Equivalent emits exist on the Vue wrapper: ready, selection-change, cell-value-change, bulk-cell-change, scroll-change, viewport-size-change, structure-change.
Renderer performance: ~70% fewer canvas calls per frame
The canvas rendering path got per-frame caches for ctx.font, ctx.fillStyle, and measureText, plus a per-frame cache for wrapped/multi-line text layout. Measured per frame during scroll on a 1200×100 sheet:
| API | Before | After | Delta |
|---|---|---|---|
ctx.font= | 129 | 34 | −74% |
ctx.fillStyle= | 179 | 46 | −74% |
ctx.measureText() | 126 | 35 | −72% |
save() / restore() | 122 | 85 | −30% |
Single-line unwrapped text takes a fast path that skips the layout cache entirely. The user-visible effect: scrolling stays at 60fps on dense sheets where it previously dipped, and CPU usage on a backgrounded grid tab is meaningfully lower.
For deep performance on first paint, the chunked xlsx loader ({ chunked: true }, added in v1.1) shrinks the user-visible freeze on big files from one multi-second block to 16–30 ms slices. We will cover that in tomorrow’s article.
Conditional formatting: per-side border overrides
Conditional format rules can now carry a border payload that draws per-side overrides on cells where the rule matches, overriding manual borders per side:
worksheet.addConditionalFormat({
range: 'D2:D100',
rule: { type: 'cellIs', operator: 'greaterThan', value: 1000 },
style: { color: '#16a34a', bold: true },
border: { right: { style: 'solid', color: '#16a34a', width: 2 } },
});
These round-trip through xlsx <dxf><border>, so the formatting survives a load-save cycle in Excel.
ReoGrid JSON: lossless native format
Introduced in v1.1 and re-exported from top-level in v1.2, writeReoGridJson/readReoGridJson capture the complete worksheet state — values, styles, borders, merges, conditional formats, filters, freeze panes, outlines, cell types, protection, alternate rows — as a self-describing JSON document. Use it for auto-save, template storage, or backend integration where xlsx is overkill.
import { writeReoGridJson, readReoGridJson } from '@reogrid/pro';
const doc = writeReoGridJson(worksheet);
await fetch('/api/save', { method: 'POST', body: JSON.stringify(doc) });
// later…
const saved = await fetch('/api/load').then(r => r.json());
readReoGridJson(worksheet, saved);
Get started
Lite is on npm; no license key required:
npm install @reogrid/lite
import { Reogrid } from '@reogrid/lite/react';
export default function App() {
return <Reogrid style={{ width: '100%', height: 500 }} />;
}
Pro adds the 109-function formula library, xlsx export, full cell types, and licensed-domain authentication. See pricing for the tier matrix, or jump to the live demos to try the features above in the browser.
The next four posts in this series go deep on specific capabilities: rendering 10,000 rows on canvas, importing real Excel files in the browser, building an editable React invoice, and how ReoGrid Web compares to the alternatives.