GitHub

XLSX

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 controls row above 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 sparse compact form (non-empty cell indexes, one text blob, and 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.

Installation

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

This pulls in @e965/xlsx (a patched, dependency-free SheetJS build on npm).

Usage

import { XlsxViewer } from "@/components/ui/xlsx-viewer";
 
export function Example() {
  return (
    <XlsxViewer
      source={{ kind: "url", url: "/workbook.xlsx", fileName: "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 sorted non-empty cell indexes, a text blob, and transferable typed arrays, so crossing the worker boundary avoids cloning every cell object.
  • Row + column virtualization. Built on Retab's local fixed-grid virtualizer: only the visible window of cells is rendered, with tight overscan and pooled rows to avoid blank flashes while scrolling.
  • Synchronous cell reads. Each visible cell is resolved against the sparse index and then read as a string slice off the compact sheet — no per-cell objects and no dense rows * columns text matrix.

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.

API Reference

PropTypeDescription
sourceUrlViewerSource | BlobViewerSourceCanonical spreadsheet source. URL sources load through fetch; Blob sources load directly from the Blob.
controlsbooleanShow the zoom/download controls. Defaults to true.
defaultSheetIndexnumberSheet shown first. Defaults to 0.
onSheetChange(index: number) => voidFires with the active sheet index on tab switch and imperative sheet changes.
activeCell{ sheet: number; row: number; col: number } | nullA cell to highlight (0-based row + column on sheet).
headerReactNodeFull-width strip below the controls (e.g. a legend).
asideReactNodeLeft rail alongside the grid.
barebooleanDrop the outer border/background so the viewer fills its container.
isolateStylesbooleanRender the scrolling grid inside a shadow root so the host page's CSS — especially :has() selectors, whose invalidation Blink doesn't scope by contain — can't reach it. On :has()-heavy pages this collapses per-scroll style recalc and keeps scrolling at refresh rate. The page's author CSS is copied in so utilities resolve and theme variables inherit through the boundary. Defaults to false. When on, host CSS can't style the grid's internals.
classNamestringOptional class on the viewer root element.

Imperative handle

Attach a ref when source-link UI needs to drive the viewer:

const ref = React.useRef<XlsxViewerHandle>(null);
 
ref.current?.scrollToCell(0, 4, 2, { behavior: "auto" });
MethodDescription
scrollToCellScrolls to public compatibility coordinates (sheet, row, col), all 0-based. Calls made before workbook metadata loads are replayed after load if the target is in bounds.
getViewportElement()Returns the scrolling grid viewport after load, or null before the viewport is mounted.

Source

xlsx-viewer.tsx