GitHub

XLSX Viewer

A virtualized spreadsheet viewer with sheet tabs, zoom, and download — parses .xlsx entirely client-side and renders only the visible cells.

The XLSX Viewer renders an Excel workbook the way the PDF Viewer renders a document: a familiar toolbar shell around the content. It parses the .xlsx (an Office Open XML zip) entirely in the browser with SheetJS — via the patched, zero-dependency @e965/xlsx republish — so there is no server and no upload.

Each sheet renders as a virtualized grid: only the rows and columns inside the scroll window are ever in the DOM, so a workbook with tens of thousands of rows stays smooth. Cells show the workbook's formatted values (numbers, dates, currencies as Excel displays them), numbers are right-aligned, and the column letters / row numbers match real spreadsheet cell references.

It is built without useEffect for data loading: the workbook loads with React's use() and Suspense. The parse runs in a Web Worker, which flattens each sheet into a compact form (one text blob plus typed arrays) and hands that back — so even a 60k-row workbook never freezes the UI thread. Cells are then read synchronously off that compact form (a typed-array index + a string slice).

Installation

pnpm dlx shadcn@latest add @retab/xlsx-viewer

This pulls in @e965/xlsx (a patched, dependency-free SheetJS build on npm) and @tanstack/react-virtual for windowing.

Usage

import { XlsxViewer } from "@/components/ui/xlsx-viewer"
 
export function Example() {
  return <XlsxViewer src="/workbook.xlsx" className="h-[600px]" />
}

Switch sheets with the tab strip along the bottom; zoom scales the whole grid (row height, column width, and font together).

How it performs

  • Off-thread parse. The workbook is parsed in a Web Worker — no conversion service, and no UI freeze. The worker flattens each sheet to a text blob + transferable typed arrays, so crossing the worker boundary costs a string clone rather than a clone of every cell object.
  • Row + column virtualization. Built on @tanstack/react-virtual: only the visible window of cells is rendered, with a small overscan to avoid blank flashes while scrolling, and rows that stay in view are skipped on re-render.
  • Synchronous cell reads. Each cell is a typed-array index plus a string slice off the compact sheet — no per-cell objects — so switching sheets is instant and memory stays flat no matter how large the sheet.

Fidelity

Values render as the workbook formats them (so 1234.5 may show as 1,234.50 and date serials as dates). Cell styling — fills, fonts, borders, conditional formatting — and charts are not rendered; this is a data viewer, not a pixel-exact Excel clone. Merged cells show their value in the top-left cell.

Props

PropTypeDescription
srcstringURL of the .xlsx/.xls (same-origin or CORS-enabled).
toolbarbooleanShow the zoom/download toolbar. Defaults to true.
downloadFileNamestringFilename for the download button.
defaultSheetIndexnumberSheet shown first. Defaults to 0.
onSheetChange(index: number) => voidFires with the active sheet index on tab switch.
headerReactNodeFull-width strip below the toolbar (e.g. a legend).
asideReactNodeLeft rail alongside the grid.
barebooleanDrop the outer border/background so the viewer fills its container.
classNamestringOptional class on the viewer shell.