Skip to main content
Enterprise Component — The SpreadsheetViewer component is available to Reducto enterprise customers. Contact [email protected] to get access.

Overview

The SpreadsheetViewer component provides a powerful, read-only Excel spreadsheet viewer with support for bounding box overlays. It’s designed to visualize Reducto extraction results directly on your spreadsheet documents.

Excel Support

View .xlsx and .xls files with full sheet navigation

Bounding Boxes

Overlay extraction results with clickable, colored regions

Zero Tailwind

CSS Variables for easy theming without Tailwind dependency

TypeScript

Full type definitions included for excellent DX

Installation

1

Configure npm registry

Create or update your project’s .npmrc file to authenticate with GitHub Packages:
.npmrc
@reductoai-collab:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${REDUCTO_NPM_TOKEN}
Never commit your token directly to .npmrc. Use an environment variable as shown above.
2

Set your authentication token

Reducto will provide you with an access token. Set it as an environment variable:
Add to your shell profile (~/.bashrc, ~/.zshrc, etc.):
export REDUCTO_NPM_TOKEN="your-token-from-reducto"
Then reload your shell:
source ~/.zshrc  # or ~/.bashrc
3

Install the package

npm install @reductoai-collab/components
4

Import the styles

Import the CSS file in your application’s entry point:
App.tsx or main.tsx
import '@reductoai-collab/components/styles/spreadsheet-viewer.css';

Quick Start

Here’s a minimal example to get you started:
import { SpreadsheetViewer } from '@reductoai-collab/components';
import '@reductoai-collab/components/styles/spreadsheet-viewer.css';

function App() {
  return (
    <div style={{ height: '600px', width: '100%' }}>
      <SpreadsheetViewer
        url="https://example.com/spreadsheet.xlsx"
        onLoad={(metadata) => {
          console.log(`Loaded ${metadata.sheetCount} sheets`);
        }}
        onError={(error) => {
          console.error(`Error: ${error.message}`);
        }}
      />
    </div>
  );
}
The SpreadsheetViewer requires a container with defined dimensions. Always wrap it in a parent element with explicit height and width.

Usage Examples

Basic Viewer

Display a spreadsheet without any overlays:
import { SpreadsheetViewer } from '@reductoai-collab/components';

function BasicViewer() {
  return (
    <div style={{ height: '600px', width: '100%' }}>
      <SpreadsheetViewer
        url="/documents/financial-report.xlsx"
        onLoad={(metadata) => {
          console.log('Sheet names:', metadata.sheetNames);
        }}
      />
    </div>
  );
}

With Bounding Boxes

Highlight specific regions of the spreadsheet with colored bounding boxes:
import { SpreadsheetViewer, type BoundingBox } from '@reductoai-collab/components';

function ViewerWithBboxes() {
  const bboxes: BoundingBox[] = [
    {
      id: 'header-row',
      page: 1,        // Sheet number (1-indexed)
      top: 1,         // Starting row
      left: 1,        // Starting column (A = 1)
      width: 5,       // Number of columns
      height: 1,      // Number of rows
      color: 'blue',
      label: 'Header',
    },
    {
      id: 'data-table',
      page: 1,
      top: 2,
      left: 1,
      width: 5,
      height: 10,
      color: 'green',
      label: 'Sales Data',
      metadata: {
        tableId: 'tbl-001',
        confidence: 0.95,
      },
    },
  ];

  return (
    <div style={{ height: '600px', width: '100%' }}>
      <SpreadsheetViewer
        url="/documents/sales-data.xlsx"
        bboxes={bboxes}
        onBboxClick={(bbox) => {
          console.log('Clicked:', bbox.id);
          console.log('Custom data:', bbox.metadata);
        }}
      />
    </div>
  );
}

Integrating with Reducto API Results

Convert Reducto extraction results to bounding boxes:
import { useState } from 'react';
import { SpreadsheetViewer, type BoundingBox } from '@reductoai-collab/components';

interface ReductoBlock {
  id: string;
  type: 'table' | 'text' | 'key_value';
  bbox: {
    page: number;
    top: number;
    left: number;
    width: number;
    height: number;
  };
  content: string;
}

interface ExtractionResult {
  blocks: ReductoBlock[];
}

function ReductoViewer({
  documentUrl,
  extractionResult,
}: {
  documentUrl: string;
  extractionResult: ExtractionResult;
}) {
  const [selectedBlock, setSelectedBlock] = useState<ReductoBlock | null>(null);

  // Map block types to colors
  const colorMap: Record<string, BoundingBox['color']> = {
    table: 'blue',
    text: 'green',
    key_value: 'purple',
  };

  // Convert Reducto blocks to bounding boxes
  const bboxes: BoundingBox[] = extractionResult.blocks.map((block) => ({
    id: block.id,
    page: block.bbox.page,
    top: block.bbox.top,
    left: block.bbox.left,
    width: block.bbox.width,
    height: block.bbox.height,
    color: colorMap[block.type] ?? 'gray',
    label: block.type,
    metadata: {
      blockId: block.id,
      type: block.type,
      content: block.content,
    },
  }));

  return (
    <div style={{ display: 'flex', height: '600px' }}>
      {/* Viewer */}
      <div style={{ flex: 1 }}>
        <SpreadsheetViewer
          url={documentUrl}
          bboxes={bboxes}
          onBboxClick={(bbox) => {
            const block = extractionResult.blocks.find(
              (b) => b.id === bbox.metadata?.blockId
            );
            setSelectedBlock(block ?? null);
          }}
        />
      </div>

      {/* Details Panel */}
      <div style={{ width: '300px', padding: '16px', borderLeft: '1px solid #e5e7eb' }}>
        <h3>Selected Block</h3>
        {selectedBlock ? (
          <div>
            <p><strong>Type:</strong> {selectedBlock.type}</p>
            <p><strong>Content:</strong></p>
            <pre style={{ whiteSpace: 'pre-wrap', fontSize: '12px' }}>
              {selectedBlock.content}
            </pre>
          </div>
        ) : (
          <p style={{ color: '#6b7280' }}>Click a bounding box to see details</p>
        )}
      </div>
    </div>
  );
}

Custom Styling with CSS Variables

Override the default styles using CSS variables:
custom-theme.css
:root {
  /* Container */
  --sv-background: #1a1a2e;
  --sv-border-color: #2d2d44;
  --sv-border-radius: 8px;

  /* Tabs */
  --sv-tab-background: #2d2d44;
  --sv-tab-background-active: #3d3d5c;
  --sv-tab-color: #a0a0b0;
  --sv-tab-color-active: #ffffff;

  /* Grid */
  --sv-header-background: #2d2d44;
  --sv-header-color: #ffffff;
  --sv-cell-background: #1a1a2e;
  --sv-cell-color: #e0e0e0;
  --sv-cell-border-color: #2d2d44;

  /* Bounding boxes */
  --sv-bbox-border-width: 2px;
  --sv-bbox-opacity: 0.15;
  --sv-bbox-opacity-hover: 0.25;
}
:root {
  /* Container */
  --sv-font-family: system-ui, -apple-system, sans-serif;
  --sv-background: #ffffff;
  --sv-border-color: #e5e7eb;
  --sv-border-radius: 4px;

  /* Tabs */
  --sv-tab-height: 36px;
  --sv-tab-padding: 0 16px;
  --sv-tab-background: #f9fafb;
  --sv-tab-background-hover: #f3f4f6;
  --sv-tab-background-active: #ffffff;
  --sv-tab-color: #6b7280;
  --sv-tab-color-active: #111827;
  --sv-tab-font-size: 13px;
  --sv-tab-font-weight: 500;

  /* Grid */
  --sv-header-background: #f9fafb;
  --sv-header-color: #374151;
  --sv-header-font-size: 12px;
  --sv-header-font-weight: 600;
  --sv-cell-background: #ffffff;
  --sv-cell-color: #111827;
  --sv-cell-font-size: 13px;
  --sv-cell-padding: 4px 8px;
  --sv-cell-border-color: #e5e7eb;
  --sv-cell-min-width: 80px;
  --sv-cell-height: 24px;
  --sv-row-header-width: 50px;

  /* Selection */
  --sv-selection-background: rgba(59, 130, 246, 0.1);
  --sv-selection-border-color: #3b82f6;

  /* Scrollbar */
  --sv-scrollbar-width: 8px;
  --sv-scrollbar-track: #f1f1f1;
  --sv-scrollbar-thumb: #c1c1c1;
  --sv-scrollbar-thumb-hover: #a1a1a1;

  /* Bounding boxes */
  --sv-bbox-border-width: 2px;
  --sv-bbox-opacity: 0.1;
  --sv-bbox-opacity-hover: 0.2;

  /* Loading & Error states */
  --sv-loading-color: #6b7280;
  --sv-error-color: #dc2626;
  --sv-error-background: #fef2f2;
}

API Reference

SpreadsheetViewer Props

url
string
required
URL to the Excel file (.xlsx or .xls). Can be a relative path, absolute URL, or blob URL.
bboxes
BoundingBox[]
Array of bounding boxes to overlay on the spreadsheet.
onLoad
(metadata: WorkbookMetadata) => void
Callback fired when the workbook is successfully loaded.
onError
(error: SpreadsheetError) => void
Callback fired when an error occurs loading or parsing the file.
onBboxClick
(bbox: BoundingBox) => void
Callback fired when a bounding box is clicked.
className
string
Additional CSS class name for the container element.
style
React.CSSProperties
Inline styles for the container element.

BoundingBox Type

interface BoundingBox {
  id: string;                    // Unique identifier
  page: number;                  // Sheet number (1-indexed)
  top: number;                   // Starting row (1-indexed)
  left: number;                  // Starting column (1-indexed, A=1)
  width: number;                 // Number of columns
  height: number;                // Number of rows
  color?: BoundingBoxColor;      // Box color (default: 'blue')
  label?: string;                // Optional label text
  metadata?: Record<string, unknown>;  // Custom data
}

type BoundingBoxColor =
  | 'blue' | 'green' | 'red' | 'yellow'
  | 'purple' | 'orange' | 'pink' | 'gray';

WorkbookMetadata Type

interface WorkbookMetadata {
  sheetCount: number;      // Total number of sheets
  sheetNames: string[];    // Array of sheet names
  activeSheet: number;     // Currently active sheet index
}

SpreadsheetError Type

interface SpreadsheetError {
  code: 'FETCH_ERROR' | 'PARSE_ERROR' | 'INVALID_FORMAT';
  message: string;
  originalError?: Error;
}

Troubleshooting

Ensure your .npmrc is configured correctly and the REDUCTO_NPM_TOKEN environment variable is set:
echo $REDUCTO_NPM_TOKEN  # Should print your token
If using a monorepo, place the .npmrc in the root directory.
If loading files from a different domain, ensure the server sends appropriate CORS headers:
Access-Control-Allow-Origin: *
Alternatively, proxy the request through your own backend or use blob URLs.
Verify that:
  1. The page property matches the sheet number (1-indexed)
  2. The top and left values are within the sheet bounds
  3. You’ve imported the CSS file
Make sure you import the CSS file in your app’s entry point:
import '@reductoai-collab/components/styles/spreadsheet-viewer.css';
If using CSS modules or scoped styles, ensure the import is global.

Support

Get Access

Contact [email protected] to get access to @reductoai-collab/components and receive your authentication token.
For technical support, reach out to your Reducto account representative or email [email protected].