Migrating from Handsontable

Handsontable's mental model is "spreadsheet-in-a-page": every cell editable, formulas, copy/paste with the OS clipboard, frozen rows and columns. Sv-grid covers ~90% of the same surface; the port is mostly a configuration translation, except for HyperFormula (see the Formulas section below).

Estimated effort: 2-4 hours per grid for typical spreadsheet workflows. Add 1-2 days if you depend heavily on HyperFormula features sv-grid doesn't ship.

Facts, checked 12 Sep 2026. handsontable 18.1.0, Commercial (see licence file), last published 1 Sep 2026, 1,190,000 npm downloads in the 30 days to 10 Sep 2026. @svgrid/grid 3.0.3, MIT, last published 11 Sep 2026, 16,900 npm downloads in the same window. Bundle, minified and gzipped, each package built alone with Svelte external: SvGrid 3.0.4 97.6 KB JS + 10.5 KB CSS (measured 22 Sep 2026). Handsontable pricing, as its site states it: Handsontable's Hobby licence is free for personal, exploratory projects and cannot be used in commercial settings; handsontable.com lists Standard from $999 per developer and Priority from $1,299 per developer, with Enterprise on custom terms (https://handsontable.com/pricing, read 12 Sep 2026). SvGrid: MIT core; @svgrid/enterprise from $299 per developer per year (Indie, under $1M revenue), $599 for Enterprise. Side by side, with sources: SvGrid vs Handsontable.

Imports

The example at the end of this page runs against these rows:

<script lang="ts">
  import { SvGrid, type GridColumns } from '@svgrid/grid'

  type Person = {
    id: number
    name: string
    department: string
    city: string
    age: number
    salary: number
  }

  const people: Person[] = [
    { id: 1, name: 'Ada Lovelace',   department: 'Engineering', city: 'London',   age: 36, salary: 142000 },
    { id: 2, name: 'Grace Hopper',   department: 'Engineering', city: 'New York', age: 45, salary: 168000 },
    { id: 3, name: 'Linus Torvalds', department: 'Platform',    city: 'Portland', age: 54, salary: 155000 },
    { id: 4, name: 'Radia Perlman',  department: 'Networking',  city: 'Seattle',  age: 49, salary: 161000 },
    { id: 5, name: 'Barbara Liskov', department: 'Platform',    city: 'Boston',   age: 52, salary: 172000 },
  ]

  let rows = $state<Person[]>(people)

  const columns: GridColumns<Person> = [
    { field: 'name',       header: 'Name',       width: 200, editorType: 'text' },
    { field: 'department', header: 'Department', width: 150, editorType: 'text' },
    { field: 'city',       header: 'City',       width: 140, editorType: 'text' },
    { field: 'age',        header: 'Age',        width: 90,  editorType: 'number' },
    { field: 'salary',     header: 'Salary',     width: 130, editorType: 'number', format: { type: 'currency', currency: 'USD' } },
  ]
</script>
- import Handsontable from 'handsontable'
- import 'handsontable/dist/handsontable.full.min.css'

+ import {
+   SvGrid, tableFeatures,
+   rowSortingFeature, columnFilteringFeature, rowSelectionFeature,
+   + } from '@svgrid/grid'

Schema → columns

Handsontable accepts a 2D array OR an array of objects. Sv-grid expects the array-of-objects form:

- const hot = new Handsontable(container, {
-   data: [
-     ['Tesla',     2017, 'black', false],
-     ['Nissan',    2018, 'blue',  true ],
-   ],
-   colHeaders: ['Make', 'Year', 'Color', 'In stock'],
-   columns: [
-     {},
-     { type: 'numeric' },
-     {},
-     { type: 'checkbox' },
-   ],
- })

+ type Car = { make: string; year: number; color: string; inStock: boolean }
+ const rows: Car[] = [
+   { make: 'Tesla',  year: 2017, color: 'black', inStock: false },
+   { make: 'Nissan', year: 2018, color: 'blue',  inStock: true  },
+ ]
+ const columns: ColumnDef<typeof features, Car>[] = [
+   { field: 'make',    header: 'Make' },
+   { field: 'year',    header: 'Year',     editorType: 'number' },
+   { field: 'color',   header: 'Color' },
+   { field: 'inStock', header: 'In stock', editorType: 'checkbox' },
+ ]
+ <SvGrid data={rows} columns={columns} features={features} />

If your existing data is a 2D array, transform once at boot:

const headerRow = raw[0]
const rows = raw.slice(1).map((cells) =>
  Object.fromEntries(cells.map((v, i) => [headerRow[i], v])),
)

Editor types

Handsontable sv-grid
type: 'text' editorType: 'text'
type: 'numeric' editorType: 'number'
type: 'date' editorType: 'date'
type: 'checkbox' editorType: 'checkbox'
type: 'dropdown', source: [...] editorType: 'list', editorOptions: [...]
type: 'autocomplete' editorType: 'list' (combobox UI ships)
type: 'time' editorType: 'time'
type: 'password' Use editorType: 'text' + a custom snippet

Selection + clipboard

Handsontable sv-grid
selectCells([[0,0,2,5]]) api.selectCells(ranges), with selectionMode='cell'
getSelected() api.getSelected(): the same [[startRow, startCol, endRow, endCol], ...], normalised to top-left / bottom-right and [] rather than undefined when nothing is selected
getSelectedLast() api.getSelectedLast(): [startRow, startCol, endRow, endCol] with the orientation kept (a range dragged upwards has startRow > endRow), or undefined
getSelectedRange() / getSelectedRangeLast() api.getSelectedRange() / api.getSelectedRangeLast(): { from, to, highlight } per rectangle, the same three fields as CellRange; plain data rather than a class, so use Math.min / Math.max for the corners
copyPaste: true (default) Ships built-in; enableCellSelection={true}. The spreadsheet shell also exchanges formats, number formats and formulas with Excel and Google Sheets through the HTML clipboard
fillHandle: true Ships built-in (drag-to-fill on selected range)
moveCells: true moveCells - drag the range border to move it, Ctrl / Cmd to copy. On by default; see move or copy a range

Frozen rows / columns

Handsontable sv-grid
fixedColumnsStart: 2 api.setColumnPinning({ left: ['firstColId', 'secondColId'] })
fixedRowsTop: 1 pinnedTopRows (and pinnedBottomRows)
manualColumnFreeze: true Ships via the column-header right-click menu
mergeCells: [{ row, col, rowspan, colspan }] mergedCells={[{ rowIndex, colIndex, rowSpan, colSpan }]} on <SvGrid>; the spreadsheet shell adds Merge & Center, Merge Across, Merge Cells and Unmerge on the ribbon and the cell menu

Freeze panes on a plain <SvGrid>: two pinned columns and the sticky letter and number headers, with formulas underneath.

Open the live example: Freeze panes (Columns)

Hooks → callbacks

Handsontable hook sv-grid
afterChange((changes, src) => {}) onCellValueChange((e) => {...})
afterSelection(...) onActiveCellChange((cell) => {...})
afterFilter(...) onFiltersChange((f) => {...})
afterColumnSort(...) onSortingChange((s) => {...})
beforeChange((changes) => false) Throw / return false from your inline validator; see validation
afterCreateRow / afterRemoveRow Wrap api.addRow / removeRow in your own emitter; on the spreadsheet shell, onChange reports a structure reason with the edit
afterMergeCells / afterUnmergeCells On the spreadsheet shell, onChange reports merges; api.getMergedCells() reads the grid's list
afterSetCellMeta (comments, validation) onChange reports comments, validation, conditional-formats and protection on the shell
afterFilter (the Filters plugin) onChange reports filter on the shell; getState().sheets[name].autoFilter is the state

On the spreadsheet shell (<SvSheet> in @svgrid/enterprise) one callback covers the lot: onChange(reasons) fires once per tick with every kind of change since the last call (cells, formats, sizes, hidden, freeze, sheets, comments, protection, validation, conditional-formats, merges, filter, structure with the insert or delete, restore), which is where Handsontable's afterChange autosave goes. getState() gives the document to save and setState() puts it back.

Cell meta → the sheet document

Handsontable keeps comments, validators, read-only flags and merges as cell meta on the instance. On the spreadsheet shell they are parts of the document, per sheet, saved with it and moved by inserts and deletes:

Handsontable sv-grid spreadsheet shell
comments: true, cell: [{ row, col, comment }] Review > New Comment, Shift+F2, the cell menu; getState().sheets[name].comments
validator: 'numeric', allowInvalid: false Data > Data Validation (Whole number, Decimal, List, Date, Text length, Custom), Stop or Warning; validation in the state
readOnly: true on a cell Format Cells > Protection unlocks the cells that may change, Review > Protect Sheet locks the rest; locked: false in the format entry, protected per sheet
mergeCells Merge & Center on the ribbon and the cell menu; merges in the state
filters: true + dropdownMenu Ctrl+Shift+L or Data > Filter: Excel's AutoFilter arrows and menus; autoFilter in the state
conditional formatting (a custom renderer) Home > Styles > Conditional Formatting: Highlight Cells, Top/Bottom, Data Bars, Color Scales, Icon Sets, Manage Rules; conditionalFormats in the state
autofill rules The fill handle's own series: numbers, dates by day or by the set gap, Item 1, weekdays and months

On a plain <SvGrid> the free grid has the primitives instead: notes and editableComments for comments, editable and valueParser on a column for read-only cells and validation, mergedCells for merges, the filter row and its Excel-style menu for filtering, and value-driven conditionalFormats by column.

Formulas (HyperFormula)

Sv-grid ships a focused subset under spreadsheet-formulas (SUM, IF, COUNTIF

If you need HyperFormula's full surface (~400 functions, complex date math, R1C1 refs), the integration pattern is:

import { HyperFormula } from 'hyperformula'

const hf = HyperFormula.buildFromArray(rowsAsMatrix, { licenseKey: 'gpl-v3' })

// On every cell edit, push to HF + read back the computed value:
function onCellValueChange(e) {
  hf.setCellContents({ sheet: 0, row: e.rowIndex, col: columnIndex }, e.newValue)
  // ...read computed cells you depend on + push back via api.setCellValue
}

This is the only piece where the swap isn't 1:1. Most teams find sv-grid's shipped subset covers 80% of real-world formula usage; if yours is in the 20%, HyperFormula plugs in with the pattern above.

Licence

Handsontable's grid needs a commercial licence for commercial use; its free Hobby licence is for personal, exploratory projects, as its pricing page states (the wording and the date are in the facts box at the top of this page). Sv-grid's @svgrid/grid is MIT with no licence key, and the Enterprise tier needs no key to evaluate: it works without one and displays a watermark and a console message until you add one, so there is no cutoff to plan around.

What you get for free vs Handsontable

What you give up

Frequently asked questions

Can SvGrid replace Handsontable's spreadsheet features?

It covers roughly 90% of the surface - inline editing on every cell, copy/paste as TSV, range selection, column groups, and a built-in formula engine. Budget 2-4 hours per grid for typical spreadsheet workflows, plus 1-2 days if you lean heavily on the full HyperFormula function set.

Does SvGrid support formulas like Handsontable?

Yes, a built-in formula engine with cell references, ranges, and common functions (see the spreadsheet-formulas guide and demo 27). It is a practical subset rather than the complete HyperFormula library, so verify your specific functions before porting.

Is SvGrid licensed like Handsontable?

No. The @svgrid/grid core is MIT and free for commercial use - no per-seat licence key - whereas Handsontable's free licence excludes commercial use and its commercial plans are per developer; the facts box has the statement as read from handsontable.com. The optional @svgrid/enterprise pack (Excel and PDF export, print, pivot, import) is priced per developer.

Does SvGrid have a blank spreadsheet like Handsontable?

Yes, on the plain grid: column-letter headers, a row gutter, a name box and formula bar, gridlines, range selection and a fill handle, with HyperFormula underneath when you want its function library.

Open the live example: Blank sheet - just type (Spreadsheet)

What you end up with

Spreadsheet behaviour: inline edit, drag a cell range, copy it out as TSV.

<SvGrid data={rows} {columns} editable enableCellSelection statusBar />

See also

Live examples

  • Freeze panes - The Excel Freeze Panes corner on a plain <SvGrid>: the Account and Owner columns stay pinned while you scroll across a full year of months, and the sticky column-letter + row-number headers stay put as you scroll down. HyperFormula keeps each row total (column O) and the bottom Total row live as you edit any month. Pinning those two columns is one prop: initialColumnPinning.
  • Blank sheet - just type - An empty Excel-style sheet on a plain <SvGrid>: column-letter headers (A..Z), a built-in 1..N row gutter, a name box + formula bar with a browsable function picker, gridlines, range selection and a fill handle. A real HyperFormula engine underneath: type a literal or a formula like =SUM(B2:D2) / =IF(...) and every dependent cell recalculates live. Drag a row or column border to resize; right-click for Cut / Copy / Paste / Clear.
  • Spreadsheet + Ribbon bar - The whole Excel surface as one component, <SvSheet workbook={wb} />: a six-tab ribbon (Home, Insert, Formulas, Data, Review, View), the Name Box and fx bar, sheet tabs and the Sum / Average / Count status bar. Format cells, merge them, comment on them, validate what goes in, colour them by rule, filter the region, protect the sheet; every button is the same call as its shortcut, so Ctrl+B and the Bold button cannot drift. A two-sheet P&L with the formats travelling in the document.

Related articles