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 first-class React and Vue wrappers and a free Lite tier on npm.
v1.4 turned the grid into a document tool. v1.5 filled in the everyday gestures. v1.6 stopped throwing things away on save. v1.7 is about the workbook somebody else made.
Both headline features came from the online xlsx viewer on this site, where people open the files they actually have and tell us when something looks wrong. One report: the sheet was too big to read and there was no way to zoom out. Another: the links didn’t open — Excel’s hyperlinks arrived as blue underlined text that did nothing, and =HYPERLINK() showed #NAME?. Several of the import fixes further down came out of the same reviews.
The rest follows the same workbook through its life. The loaders that quietly opened only a workbook’s first sheet are renamed, so the obvious call opens every sheet. The whole workbook goes into one PDF. A form’s empty bordered boxes count toward what gets printed. An exported xlsx opens in Excel looking like the grid. And on iPad and iPhone, typing into cells and downloading files now work properly.
There are no breaking public-API changes. Three behaviours do change, and they are listed under Upgrade at the end.
Zoom — 10% to 400%, per sheet
Excel’s zoom box, in both editions:
import { createReogrid } from '@reogrid/pro';
const grid = createReogrid({ workspace: '#app' });
const ws = grid.worksheet;
ws.setZoom(1.5); // 150% — clamped to 10%–400%
ws.getZoom(); // 1.5
// Keep a "150%" readout in your toolbar in step
grid.onZoomChange(zoom => {
label.textContent = `${Math.round(zoom * 100)}%`;
});
// Zoom is per sheet, and switching tabs is not a change — re-read it there too
grid.workbook.onActiveSheetChange(() => {
label.textContent = `${Math.round(grid.worksheet.getZoom() * 100)}%`;
});
With the mouse, Ctrl+wheel (Cmd+wheel on macOS) zooms around the pointer, so the part of the sheet you are looking at stays under the cursor. One notch of a mouse wheel is about 15%. A trackpad pinch goes through the same path, because browsers report a pinch as a Ctrl+wheel event. Each sheet keeps its own zoom, just as each Excel sheet does, and it is stored in the xlsx as zoomScale, so a workbook saved at 85% opens at 85%, and saves back at 85%.
The design decision that matters: only the view scales. Row heights, column widths, font sizes, scroll offsets and hit-testing all stay in 100% sheet pixels; the zoom is applied only where the sheet meets the screen. So a zoomed sheet prints the same, exports to PDF and xlsx the same, and auto-fits the same. Grid lines stay 1px wide at any zoom, as in Excel. The grab zones stay the same size on screen too — column and row borders, the selection border you drag to move, the fill handle, image grips — so they don’t get harder to hit at 50%.
Zoom is a viewer setting, not content: ReoGrid JSON does not store it, and reset() / loadJson() leave it where it was. There is no keyboard shortcut, because Ctrl+plus, minus and 0 belong to the browser.
Two things to know when you upgrade.
-
Ctrl/Cmd+wheel over the grid now zooms the sheet. Until 1.6 it fell through to the browser’s page zoom. To keep that, opt out —
setZoom()keeps working either way:const grid = createReogrid({ workspace: '#app', wheelZoom: false }); -
Overlay rectangles stay in 100% pixels. If you place your own elements over the grid using
getCellRect(),getRangeRect()orgetImageRect(), multiply by the zoom:const z = ws.getZoom(); const r = ws.getCellRect(4, 2); // 100% sheet px, whatever the zoom Object.assign(badge.style, { left: `${r.x * z}px`, top: `${r.y * z}px`, width: `${r.width * z}px`, height: `${r.height * z}px`, });
Both editions. See the zoom docs and the live demo — or open a file in the xlsx viewer and Ctrl+wheel over the table.
Hyperlinks that open
Links in an xlsx file are now read, shown and kept. Click a linked cell and the address opens in a new tab. Click and hold to select the cell instead, as in Excel. Hovering shows a pointer and a tooltip with the address the link goes to (under the link’s own screen tip, if it has one), so you can see where a click will take you before you click. Excel’s =HYPERLINK(address, [label]) works: the cell shows the label and opens the address.
In code, a link is a property of the cell, not a cell type: the value and formatting stay as they are. That is also why links arriving from xlsx keep the look they had in Excel.
const ws = grid.worksheet;
// Link a cell. Its value and formatting are left alone — style it yourself.
ws.setCellInput(1, 0, 'Release notes');
ws.setCellStyle(1, 0, { color: '#0563C1', underline: true });
ws.setHyperlink(1, 0, 'https://web.reogrid.net/release-notes/', { tooltip: 'Full changelog' });
// Or Excel's own function
ws.setCellInput(2, 0, '=HYPERLINK("mailto:[email protected]", "Contact sales")');
// Every click goes through here first
grid.onHyperlinkClick(e => {
if (!confirm(`Open ${e.url}?`)) e.cancel = true;
});
What gets opened is deliberately narrow. Only absolute http, https, mailto and tel addresses are opened, and the new tab is opened with noopener,noreferrer: the page it loads gets no handle back to yours, and no Referer naming it. Anything else — javascript:, data:, a file: path, a path relative to the workbook — shows a short message on the cell instead. onHyperlinkClick receives the address as stored, before that check, so cancel also lets you handle in your app the addresses ReoGrid itself won’t open.
The older hyperlink cell type now goes through the same check (relative paths still work there, resolved against the page). Until 1.7 it passed its address straight to window.open and wrote it into <a href> on HTML export, so a javascript: address would run in the page.
Links follow row and column inserts and deletes, sorting and drag-to-move, and round-trip through ReoGrid JSON (sheet.hyperlinks) as well as xlsx.
Not there yet: a link to a place inside the workbook (Sheet2!A1) is kept and saved back, but clicking it does not jump yet. Copy and paste does not carry links.
If you embed the grid in a sandboxed <iframe>, the browser blocks the new tab unless the sandbox includes allow-popups. Add allow-popups-to-escape-sandbox as well, or the opened page inherits your sandbox.
Both editions for loading, showing and clicking links. Creating links with setHyperlink is Pro, and so is =HYPERLINK(), since Lite has no built-in functions (it shows #NAME? there). See the hyperlinks docs and the live demo.
Opening a file opens the whole workbook
This one was a trap in our own API.
The worksheet had loaders named exactly like the instance’s: grid.worksheet.loadFromFile(file) next to grid.loadFromFile(file). They read the same, but the worksheet one imports a single sheet — the first — with no sheet tab bar and no way to reach the others. A workbook whose first tab is a cover page or an index came up looking blank, and nothing said why.
The single-sheet loaders now say what they do:
| Before (deprecated) | Now |
|---|---|
worksheet.loadFromFile | worksheet.loadSheetFromFile |
worksheet.loadFromUrl | worksheet.loadSheetFromUrl |
worksheet.loadXlsx / loadFromBuffer | worksheet.loadSheetFromBuffer |
// The whole workbook — every sheet, with the sheet tab bar
await grid.loadFromFile(file);
// One sheet, on purpose
await grid.worksheet.loadSheetFromFile(file, { sheetName: 'Summary' });
To open a file, use the instance methods — grid.loadFromFile(), loadFromUrl(), loadXlsx(). Use worksheet.loadSheet* only when you really want one sheet. The old names still work exactly as before. They are marked @deprecated, so editors strike them through, and print a one-time console warning that names both alternatives. They will be removed in 2.0.
Both editions. See the xlsx I/O docs.
Imported workbooks look more like Excel
Three import fixes from the same viewer reviews:
- Plain cells use the workbook’s default font. A cell Excel saves without a style takes the workbook’s Normal style, but the importer left it in ReoGrid’s own Arial 10. In a workbook whose Normal font is Candara 12, the untouched cells of a table came out in a different font and size from the formatted cells next to them. They now open in the workbook’s font — for a typical Excel file, Calibri 11, 游ゴシック 11 or MS Pゴシック 11. Empty cells that are not in the file at all still start in ReoGrid’s default when you type into them.
- A table set to Excel’s “None” style is shown plain. The importer read a missing style name as the default
TableStyleMedium2, so a table that is plain in Excel came up with a blue header and banded rows. It now paints nothing, and saves back without a style.addTable(range, { style: '' })creates one (Pro). - Formulas ReoGrid can’t read show Excel’s result, not their text. A formula that refers to another workbook (
=[1]Sheet1!A1*2) or uses a structured table reference (=SUM(Table1[Qty])) used to show its formula text in the cell. It now shows the result Excel saved in the file, other formulas can use that value, and the formula itself is kept and written back on export. It is not recalculated, and editing the cell replaces it. A quoted reference to another workbook (='[1]My Sheet'!A1) is no longer mistaken for a missing local sheet and turned into#REF!.
The whole workbook in one PDF
exportPdf and saveAsPdf take a new sheets option. It does what Excel’s “Print Entire Workbook” does:
import { createReogrid, preloadPdfFont } from '@reogrid/pro';
await preloadPdfFont('ja');
// Every visible sheet, each with its own page setup
grid.saveAsPdf({ locale: 'ja', usePageBreaks: true, sheets: 'all' });
// Or exactly these sheets, in this order — by name or index
const bytes = grid.exportPdf({ locale: 'ja', sheets: ['Quote', 2] });
'all' takes every visible sheet in tab order. A list takes exactly the sheets you name, in your order, and includes a hidden sheet if you name it. The default is still 'active', so existing calls are unchanged.
With usePageBreaks, each sheet paginates on its own terms — its own paper size, orientation, margins, fit-to-pages and page breaks — so one document can mix A4 portrait and A3 landscape pages. Page numbers run on across sheets: in a header or footer, &P counts from the document’s first page and &N is the document’s total, the way Excel numbers a whole-workbook print, while &A still names each page’s own sheet.
grid.worksheet.setPrintSettings({
headerFooter: { footer: { left: '&A', right: 'Page &P of &N' } },
});
Sheets with nothing to print are skipped, as in Excel. For build scripts and servers, the headless counterpart is exportWorksheetsPdf(worksheets, options).
Pro. See the PDF export docs, or try it without code: the xlsx → PDF tool has an All sheets option.
Forms print whole
With no print area set, the printed range used to run from A1 to the last cell holding a value. A typical form reaches further than that: a bordered table whose rows are still empty, a merged remarks field whose text sits in its first cell, a stamp box, a logo. So the page-break preview, PDF export and browser print cut the form off at its last value, and fit-to-pages shrank the sheet to fit only that part.
The printed range now reaches every cell that leaves a mark on paper: values, borders, fills, the full extent of merged cells, and images. Formatting that prints nothing — a font, a number format, a white fill — still doesn’t extend it, so it can’t add blank pages. worksheet.getPrintContentExtent() reports the result, and every print and PDF path now uses the same range.
Pro. See the page layout docs.
An exported xlsx looks the same in Excel
Opening an exported workbook in Excel showed several differences from the grid. All of them were in the writer, and all are fixed:
- Row heights were written in pixels where the format expects points, so every row came out a third taller in Excel — and a third taller again on each re-import. A round trip now gives back the same heights.
- Number formats —
#,##0,[$¥]#,##0, dates, percentages — were not written at all; every cell was General. They are now saved, including on empty input cells that carry a format. - Numbers typed into cells (
135000) were written as text, so Excel neither calculated with them nor applied their format. Plain decimals are now numbers; text that only looks numeric to JavaScript, like007or1e3, stays text. - Line breaks. Excel shows a line break only when wrap text is on, so a multi-line address block came out on one line. A value containing a line break now gets wrap text.
- Empty styled cells were written as empty strings. Excel counts those as filled, so a right-aligned label could not spill left across them and was cut off. They are now written without a value.
Pro. See the xlsx I/O docs.
iPad and iPhone
- Typing is no longer auto-capitalised or auto-corrected. With a hardware keyboard, iPadOS applied sentence capitalisation and auto-correct to the cell editor, so
m2was stored asM2. The editor now opts out of both, and of spellcheck. - No more double-tap zoom. Tapping one cell quickly after another no longer zooms the page, and the grey tap flash over the grid is gone.
- Downloads work in Safari.
saveAsPdfandsaveAsXlsxreleased the file before Safari’s “View or Download?” step had read it, so nothing was saved. The file now stays available long enough. - Printing works on iPad. iPadOS lays out the pages while its print sheet is open, and by then the grid could already have removed the hidden page it was printing.
Japanese input and forms
- 全角数字 are numbers. With the IME on,
120went into the cell as text: a formula multiplying it came out blank, and a decimal validation rejected it. As in Excel, an entry that is a number once its full-width digits (and + - .) are converted is now stored as half-width120. Text that merely contains one, like3階, is kept as typed. - One Enter in a form. The Enter that confirms an IME composition belongs to the IME, so a quantity field in a form took two Enters: one to confirm
120, one to move on. WithformNavigationon, that Enter now also commits and moves to the next field when the cell holds just a number. Text still only confirms, so you can keep typing after confirming a word. - A dropdown pick is undoable. Picking from a cell’s ▼ list with the mouse or a tap wrote the cell directly, so Ctrl/Cmd+Z could not take it back. It now goes through the same path as a keyboard pick, so it can be undone, and in a form it moves on to the next field.
And the rest
- A bold label after a dropdown keeps its bold. A dropdown cell changed the canvas font without restoring it, so the cells drawn after it could lose their bold or their colour. Every cell-type handler is now fenced off, including your own from
registerCellTypeHandler. - Overwriting a formula with a value recalculates the formulas that read it, instead of leaving them on their old result until the next full recalculation.
- Lite’s “Pro feature” console warning links to a page that exists — the pricing page — rather than a 404.
- Real npm READMEs.
@reogrid/pronow ships a full README instead of a placeholder, the@reogrid/liteREADME is rewritten aroundcreateReogrid(), and both packages state their license plainly: Lite is free with commercial use OK, Pro is a paid license, and neither is open source.
Upgrade
There are no breaking public-API changes, but three behaviours change:
- Ctrl/Cmd+wheel over the grid zooms the sheet instead of the browser page. Opt out with
createReogrid({ wheelZoom: false }). If you overlay elements usinggetCellRect()/getRangeRect()/getImageRect(), multiply byworksheet.getZoom(). - Plain cells in an imported xlsx use the workbook’s default font instead of Arial 10.
- Numbers typed in full-width digits are stored as numbers.
And the worksheet-level loadXlsx / loadFromBuffer / loadFromFile / loadFromUrl are deprecated: they still work, warn once, and will be removed in 2.0. Switch to grid.loadFromFile() and friends to open a workbook, or worksheet.loadSheetFrom* for one sheet.
npm i @reogrid/[email protected]
Lite is on npm with no license key:
npm i @reogrid/[email protected]
See the release notes for the complete v1.7 changelog; the zoom, hyperlinks, PDF export, page layout and xlsx I/O docs for the full APIs; and the zoom and hyperlinks demos. Or open your own file in the xlsx viewer — links open and Ctrl+wheel zooms — and turn a whole workbook into one PDF with the xlsx → PDF tool. The pricing matrix has the Lite vs Pro breakdown.