ReoGrid ReoGrid Web

When Excel is your layout tool: move merged-cell forms to the web without rebuilding them

· unvell team
When Excel is your layout tool: move merged-cell forms to the web without rebuilding them

A surprising amount of what people call a “spreadsheet” is not a data table at all — it’s a form. Quotations, invoices, purchase orders, expense claims, inspection sheets: someone squared up the cells, merged them into boxes, drew borders, and used Excel as a sheet of graph paper. The cells hold layout, not data.

When a requirement lands to “put this form in the web app,” the reflex is to rebuild it: HTML tables, CSS to approximate the borders, a PDF library for printing. You quickly drown in rowspan/colspan, the borders shift when printed, the column widths never quite match, and you reimplement the formulas by hand. The premise — that you have to rebuild it — is the mistake.

ReoGrid Web takes a different route: treat the form as an actual spreadsheet, preserving merged cells, borders, and number formats, and read or write it right in the browser. This article shows the two practical paths — load the existing .xlsx as-is, or build it in code — with real examples.

Prefer the high-level overview first? See Excel-like business forms on the web for the three approaches at a glance. This article is the hands-on, code-first companion.


Why layout-shaped spreadsheets are hard to “web-ify”

A plain data grid and a business form are different animals, because a form carries layout, not just data:

  • Merged cells run across rows and columns to build title bars and label boxes
  • Borders — their weight and placement — are part of the format
  • Column widths and row heights are fixed, so the printed result looks a certain way
  • Cells contain formulas (subtotals, tax, totals)

Approximate all of that with an HTML table and it looks roughly right but never quite feels like the original. Worse, you can’t reuse the Excel template the business already has — you’re recreating a years-old invoice layout pixel by pixel.


Path A: load the existing form .xlsx, untouched

The fastest move is to not rebuild at all. Load the existing .xlsx template straight into the grid.

import { createReogrid } from '@reogrid/lite';

const { worksheet } = createReogrid('#grid');

// From a file the user picks
const input = document.querySelector<HTMLInputElement>('#file')!;
input.addEventListener('change', async (e) => {
  const file = (e.target as HTMLInputElement).files?.[0];
  if (file) await worksheet.loadFromFile(file);
});

// Or from a template on the server
await worksheet.loadFromUrl('/templates/quotation.xlsx');

loadFromFile resolves after merged cells, borders, number formats, row/column sizes, and embedded images are all applied. You get the form’s exact appearance preserved, rendered as an editable grid — zero “porting” work into HTML.

And it all runs in the browser: the file never leaves the user’s device. Being able to handle confidential business forms without uploading them to a server is a real-world win.

Importing works on the free Lite tier. See the XLSX I/O docs.


Path B: build the form in code

When there’s no template, or you want to generate the document dynamically, build it in code. The key is that ReoGrid is a real spreadsheet, not a data grid — merging, borders, widths, number formats, and formulas are all part of the API.

Here’s a complete quotation built from scratch:

import { createReogrid, NumberFormat } from '@reogrid/lite';

const { worksheet } = createReogrid('#grid');
const money = NumberFormat.currency('$', 2); // 300000 → $300,000.00

worksheet.suspendRender(); // batch the build (no flicker)

// ── Column widths: define meaningful columns instead of graph paper ──
worksheet.column('A').width = 48;   // No.
worksheet.column('B').width = 260;  // Description
worksheet.column('C').width = 72;   // Qty
worksheet.column('D').width = 120;  // Unit price
worksheet.column('E').width = 140;  // Amount

// ── Title bar (merge A1:E1) ──
worksheet.range('A1:E1')
  .merge()
  .setValue('QUOTATION')
  .setStyle({ bold: true, fontSize: 20, textAlign: 'center', verticalAlign: 'middle', color: '#1e3a5f' });
worksheet.row(0).height = 44;

// ── Recipient / meta ──
worksheet.cell('A3').setValue('Acme Corp.').setStyle({ bold: true, fontSize: 12 });
worksheet.cell('D3').setValue('Quote #').setStyle({ bold: true, textAlign: 'right' });
worksheet.cell('E3').setValue('Q-2026-0042');
worksheet.cell('D4').setValue('Date').setStyle({ bold: true, textAlign: 'right' });
worksheet.cell('E4').setValue('2026-06-04');

// ── Line-item header ──
worksheet.range('A6:E6').setStyle({ bold: true, textAlign: 'center', backgroundColor: '#eef2f7', color: '#1e3a5f' });
worksheet.cell('A6').setValue('No.');
worksheet.cell('B6').setValue('Description');
worksheet.cell('C6').setValue('Qty');
worksheet.cell('D6').setValue('Unit price');
worksheet.cell('E6').setValue('Amount');

// ── Line items ──
const items = [
  { name: 'Website build — setup',   qty: 1,  price: 30000 },
  { name: 'Maintenance (monthly)',   qty: 12, price: 2000 },
  { name: 'Domain & SSL certificate', qty: 1, price: 1500 },
];
items.forEach((it, i) => {
  const r = 7 + i;                                  // rows 7, 8, 9 (A1 style)
  worksheet.cell(`A${r}`).setValue(i + 1).setStyle({ textAlign: 'center' });
  worksheet.cell(`B${r}`).setValue(it.name);
  worksheet.cell(`C${r}`).setValue(it.qty).setStyle({ textAlign: 'center' });
  worksheet.cell(`D${r}`).setValue(it.price);
  worksheet.setCellInput(r - 1, 4, `=C${r}*D${r}`); // amount = qty × unit price
});

// ── Subtotal / tax / total ──
worksheet.cell('D10').setValue('Subtotal').setStyle({ bold: true, textAlign: 'right' });
worksheet.setCellInput(9, 4, '=E7+E8+E9');
worksheet.cell('D11').setValue('Tax (10%)').setStyle({ bold: true, textAlign: 'right' });
worksheet.setCellInput(10, 4, '=E10*0.1');
worksheet.cell('D12').setValue('Total').setStyle({ bold: true, textAlign: 'right', fontSize: 12 });
worksheet.setCellInput(11, 4, '=E10+E11');

// ── Currency format on price/amount columns ──
worksheet.range('D7:D9').setFormat(money);
worksheet.range('E7:E12').setFormat(money);

// ── Borders ──
worksheet.range('A6:E9').border({ style: 'solid', color: '#cbd5e1' });                     // table body
worksheet.range('A6:E6').border({ style: 'solid', color: '#1e3a5f', width: 2 }, ['bottom']); // header rule
worksheet.range('A12:E12').border({ style: 'solid', color: '#1e3a5f', width: 2 }, ['top']);  // total rule

worksheet.resumeRender();

A few things worth calling out:

  • range('A1:E1').merge() collapses a range into one logical cell. The value and style live on the anchor (top-left). See the Merging Cells docs.
  • Formulas go through setCellInput() — its arguments are 0-based (row, column), while the cell references inside the formula are A1-style (1-based rows). setValue('=C7*D7') would store the literal text =C7*D7.
  • Arithmetic like =C7*D7 works on the free Lite tier. Built-in functions such as SUM are Pro (you could write the subtotal as =SUM(E7:E9)).
  • NumberFormat.currency('$', 2) produces $300,000.00. Symbol and position are configurable — see the Number Formatting docs.

Locale-specific formats come along for free

Because the format codes are Excel-compatible, locale conventions are just format strings — including ones Western tooling rarely handles, like Japanese era dates and red-triangle negatives common in Asian financial statements.

// Red negative — show losses as a red triangle (common in P&L statements)
worksheet.range('E7:E12').setFormat('#,##0;[Red]▲#,##0');

// Japanese era date — e.g. Reiwa 6 / 令和6年6月4日
worksheet.cell('E4').setFormat('ggge"年"m"月"d"日"');

See the Japanese era & color formats demo for era dates (令和 / 平成 / 昭和) and [Red] / [赤] bracket colors in a working P&L sheet.


Round-trip back to Excel

In most businesses the request eventually comes: “just send me the Excel.” Export the form your users edited in the browser back to .xlsx.

import { createReogrid } from '@reogrid/pro';

const { worksheet } = createReogrid('#grid');
// ...load / edit the form...

worksheet.saveAsXlsx({
  filename: 'quotation_2026-06-04.xlsx',
  sheetName: 'Quote',
});

The loadXlsx → edit → saveAsXlsx round-trip preserves cell values, formulas (with cached results so Excel shows numbers immediately), styles, number formats, merged cells, borders, row/column sizes, conditional formatting, and embedded images. The form you loaded comes back out looking the same. Export is a Pro feature (see the save-as-xlsx recipe).


How this differs from rebuilding as an HTML table

Rebuild as HTML table + CSSLoad / build with ReoGrid Web
Merged cellshand-written rowspan/colspanmerge() / preserved from the xlsx
Borders & widthsapproximated in CSS (shift on print)per-cell, matches Excel
Formulas (subtotal/tax/total)reimplement yourselfbuilt-in formula engine
Reusing an existing Excel templatenot possible — rebuild from zeroloadFromFile, as-is
Writing back to Excelneeds a separate librarysaveAsXlsx (Pro)
Cell-editing UIbuild your own input/contenteditablecell editing built in

If the goal is to reproduce Excel’s look and feel on the web, starting from a grid means far less to build and far less to maintain.


Lite vs Pro

OperationLite (free)Pro
xlsx import (loadFrom*)
Merge, borders, formats, widths
Arithmetic formulas (=C7*D7)
Built-in functions (SUM, etc.)
xlsx export (saveAsXlsx)

In short: reading, displaying, and editing layout-shaped forms in the browser is free; writing back to Excel is the Pro line.


Wrapping up

A form-shaped spreadsheet carries layout, not data. Rather than rebuilding it as an HTML table, treat it as a spreadsheet — load the existing .xlsx (Path A) or build it in code (Path B). Merged cells, borders, currency formats, and locale conventions all come along, entirely in the browser.

Try the Merge & Layout demo, start from the Build an Invoice recipe, or browse all the live demos.

Try ReoGrid Web in your project

Canvas-based Excel-compatible spreadsheet component for React and Vue. Lite is free — start with one npm install.

Related articles

Stay Updated

Be first to know — get updates as they ship

Get notified of new releases, features, and announcements.
No spam — just updates that matter.