Spreadsheet TypeScript helper for React
v1.0.0Added in 1.0.0
Read CSV and Excel files in the browser, match their columns to your fields, and write CSV that Excel opens with diacritics intact.
Choose a CSV or an .xlsx file (or keep the sample, a ';' export from a European Excel): the demo shows the detected delimiter and encoding, the header row, how each column matched a Northwind CRM field (Company name → Company, VAT ID → Tax ID) with its score, and the first rows. Download CSV writes the parsed rows back with the delimiter from the playground, a byte order mark and formula cells neutralised.
Spreadsheet is a Pro React helper in the React hooks and utilities category, written in TypeScript for React 19. It installs as source with npx wingo-ui@latest add spreadsheet, together with Text match and the npm package read-excel-file.
Add to a project
Requirements: React 19, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup
npx wingo-ui@latest add spreadsheetSpreadsheet is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans
Usage
Import readSpreadsheet from @/lib/spreadsheet.
import { matchColumns, readSpreadsheet, SpreadsheetError, toCsv } from "@/lib/spreadsheet"
async function onFile(file: File) {
try {
const { sheets } = await readSpreadsheet(file)
const [sheet] = sheets
const mapping = matchColumns(sheet.headers, [
{ id: "name", label: "Company", aliases: ["Company name", "Customer"] },
{ id: "taxId", label: "Tax ID", aliases: ["VAT ID", "VAT number"] },
])
return { sheet, mapping }
} catch (error) {
if (error instanceof SpreadsheetError && error.code === "unsupported") return null
throw error
}
}
toCsv([["Customer", "Balance"], ["Northwind Tech LLC", 12480]], { delimiter: ";" })API
delimiterdefault","stringtoCsv options.delimiter for the download: "," or ";" (what a Romanian Excel expects, because "," is its decimal mark).
escapeFormulasdefaulttruebooleantoCsv options.escapeFormulas: cells starting with = + - @ get a leading ' so a spreadsheet never runs them (CSV injection).
thresholddefault0.5numbermatchColumns options.threshold: the lowest score that still maps a column to a field.
readSpreadsheet(file: Blob & { name?: string }, options?: { maxRows?; maxBytes?; header?: "auto" | number | false; delimiter?; columnLabel? }) => Promise<SpreadsheetWorkbook>A picked file as { kind: "csv" | "xlsx", sheets: { name, headers, rows, truncated }[], delimiter?, encoding? }. CSV / TSV / TXT are decoded as UTF-8, else Windows-1250 (a Romanian Excel), with the delimiter detected; .xlsx loads read-excel-file on demand. Blank and duplicate headers get names, short rows are padded, empty rows dropped. Throws SpreadsheetError with code "unsupported" (.xls, .numbers), "too-large" (default 20 MB), "empty" or "parse".
Parts
parseCsv
functionRFC 4180 parsing (quotes, doubled quotes, line breaks inside quotes, CRLF, a BOM), the delimiter among , ; tab | that splits the first lines most evenly, and the text of a file's bytes. Signature: (text, { delimiter?, maxRows? }) => { rows: string[][]; delimiter }.
detectDelimiter
functionRFC 4180 parsing (quotes, doubled quotes, line breaks inside quotes, CRLF, a BOM), the delimiter among , ; tab | that splits the first lines most evenly, and the text of a file's bytes. Signature: (text) => string.
decodeText
functionRFC 4180 parsing (quotes, doubled quotes, line breaks inside quotes, CRLF, a BOM), the delimiter among , ; tab | that splits the first lines most evenly, and the text of a file's bytes. Signature: (buffer: ArrayBuffer) => { text; encoding }.
toSheet
functionRaw rows to a sheet with a header row, and a cell as text for previews and validators (dates as "2026-09-24"). Signature: (name, rows, options?) => SpreadsheetSheet.
cellToString
functionRaw rows to a sheet with a header row, and a cell as text for previews and validators (dates as "2026-09-24"). Signature: (cell: SpreadsheetCell) => string.
matchColumns
functionPairs each field with at most one column by name, ignoring case, diacritics and punctuation ("Company name" = "company_name", "Société" = "societe"), aliases included ("VAT ID", "Tax ID"), best scores first, each column used once. The auto-match of an import wizard. Signature: (headers: string[], targets: { id; label; aliases? }[], { threshold? }) => { targetId; column: number | null; score }[].
toCsv
functionCSV text with quoting where needed, a UTF-8 BOM by default so Excel keeps accented letters (é, ü, ñ), and a browser download (nothing happens on the server). Signature: (rows, { delimiter?, bom?, lineEnding?, escapeFormulas? }) => string.
downloadCsv
functionCSV text with quoting where needed, a UTF-8 BOM by default so Excel keeps accented letters (é, ü, ñ), and a browser download (nothing happens on the server). Signature: (filename, rowsOrText, options?) => void.
SpreadsheetError
typeWhat readSpreadsheet throws, so a UI can print its own message per code. Type: class extends Error { code: "unsupported" | "empty" | "too-large" | "parse" }.
Examples
Export to CSV
Quotes, accented names and a formula-looking note come out safe for Excel; the button saves orders.csv with a UTF-8 BOM.
Dependencies
Files
- lib/spreadsheet.ts
FAQ
Is the Spreadsheet helper free?
The preview, props and docs are free; copying the source needs a Pro plan. See plans.
How do I install Spreadsheet?
Run npx wingo-ui@latest add spreadsheet. It also adds the Wingo UI item it builds on (Text match) and the npm package read-excel-file. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/spreadsheet.
See also
Used in
More React hooks and utilities
Articles using this component
- React CSV Importer: Upload, Map Columns, Fix Rows, Import
Component guides7 min read
- React Excel Viewer: Show .xlsx Files With Their Real Styles
Component guides10 min read