Spreadsheet 2 - Composable, declarative Spreadsheet component.

Beautifully designed Spreadsheet component for React. Composable. Customizable. Declarative

Current version:
@rowsncolumns/spreadsheet 6.2.125
@rowsncolumns/spreadsheet-state 16.0.132
fx

Sheet 1
Sheet 2

Built for Developers

Spreadsheet is rendered in HTML Canvas, giving you ~60fps rendering performance and ability to display millions of rows and columns without peformance impact. With escape hatches, you can access the internals and customize it to your liking.

Compose the perfect Spreadsheet

Pick and choose the components you need to build your Spreadsheet.

You can also build own custom functionality on top of Spreadsheet 2, with the many callbacks from CanvasGrid.

import { 
  SpreadsheetProvider, CanvasGrid, Toolbar, ButtonUndo, 
  ButtonRedo, RangeSelector, FormulaBarLabel, FormulaBarInput, 
  BottomBar, SheetSwitcher, SheetTabs, SheetStatus 
} from "@rowsncolumns/spreadsheet"

const App = () => {
  const sheet = { id: 1, rowCount: 1000, columnCount: 1000 }
  return (
    <>
      <Toolbar>
        <ButtonUndo onClick={} />
        <ButtonRedo onClick={} />
        {/*..import all other controls*/}
      </Toolbar>
      <FormulaBar>
        <RangeSelector />
        <FormulaBarInput />
      </FormulaBar>
      <CanvasGrid
        sheetId={sheet.id}
        rowCount={sheet.rowCount}
        columnCount={sheet.columnCount}
        getCellData={(sheetId: number, rowIndex: number, columnIndex: number): CellData => {
          return {
            userEnteredValue: {
              formulaValue: '=SUM(4,4)'
            },
            formattedValue: "8"
          }
        }}
      />
      <BottomBar>
        <SheetSwitcher />
        <SheetTabs />
        <SheetStatus />
      </BottomBar>
    </>
  )
}

{/* Wrap your app in SpreadsheetProvider */}
export const Spreadsheet = () => (
  <SpreadsheetProvider>
    <App />
  </SpreadsheetProvider>
)

Bring your own Data model and State management

Or use `useSpreadsheetState` hook

Spreadsheet components are all uncontrolled and stateless. It renders based on the props that you pass-in and invokes callbacks to user actions.

Spreadsheet 2 is agnostic of your data persistence model. You can choose any database for storage or real-time collaboration.

import type { CellData } from "@rowsncolumns/spreadsheet"
import {
  useSpreadsheetState,
  Sheet,
  SheetData,
  RowData
} from "@rowsncolumns/spreadsheet-state"

type MyCellData = CellData & {
  customProperty: 'hello'
}
type SheetData<T extends CellData> = Record<number, RowData<T>[]>
type RowData<T> = {
  values: T[]
}

const App = () => {
  const [ sheets, onChangeSheets ] = useState<Sheet[]>([])
  const [ sheetData, onChangeSheetData ] = useState<SheetData<MyCellData>>({ })
  return (
    <CanvasGrid<MyCellData>
      getCellData={(sheetId, rowIndex, columnIndex) => {
        return sheetData[sheetId]?.[rowIndex]?.values?.[columnIndex]
      }}
      onChange={(value: string, sheetId: number, rowIndex: number, columnIndex: number) => {
        // Persist and generate undo/redo patches
        // If you are using useSpreadsheetState hook,its all built-in
        onChangeSheetData(prevData => ...)
      }}
    />
  )
}

{/* Wrap your app in SpreadsheetProvider */}
export const Spreadsheet = () => (
  <SpreadsheetProvider>
    <App />
  </SpreadsheetProvider>
)

useSpreadsheetState hook

This is an optional (Production grade) hook if you want to get started with the Spreadsheet with complete state management.

  • Uses immer for state management.
  • Full undo/redo capability.
  • Calculation framework with optional web worker support.
  • Real time collaboration built-in.
import { 
  defaultSpreadsheetTheme,
  Sheet,
  SheetData,
} from "@rowsncolumns/spreadsheet-state"
import { 
  CellData,
  NamedRange,
  EmbeddedObject,
  EmbeddedChart,
  TableView,
  SpreadsheetTheme,
  CanvasGrid,
  SpreadsheetProvider
} from "@rowsncolumns/spreadsheet"

const App = () => {
  const [sheets, onChangeSheets] = useState<Sheet[]>([]);
  const [sheetData, onChangeSheetData] = useState<SheetData<CellData>>({});
  const [scale, onChangeScale] = useState(1);
  const [colorMode, onChangeColorMode] = useState<ColorMode>();
  const [charts, onChangeCharts] = useState<EmbeddedChart[]>([]);
  const [embeds, onChangeEmbeds] = useState<EmbeddedObject[]>([]);
  const [tables, onChangeTables] = useState<TableView[]>([]);
  const [namedRanges, onChangeNamedRanges] = useState<NamedRange[]>();
  const [theme, onChangeTheme] = useState<SpreadsheetTheme>(defaultSpreadsheetTheme);
  const locale = "en-GB";
  const currency = "USD";
  const {
    activeCell,
    activeSheetId,
    selections,
    rowCount,
    columnCount,
    frozenColumnCount,
    frozenRowCount,
    rowMetadata,
    columnMetadata,
    merges,
    protectedRanges,
    bandedRanges,
    conditionalFormats,
    isDarkMode,
    spreadsheetColors,
    canRedo,
    canUndo,
    undo,
    redo,    
    ...// other Spreadsheet methods
  } = useSpreadsheetState({
    sheets,
    sheetData,
    tables,
    functions,
    namedRanges,
    theme,
    colorMode,
    locale,
    onChangeSheets,
    onChangeSheetData,
    onChangeEmbeds,
    onChangeCharts,
    onChangeTables,
    onChangeNamedRanges,
    onChangeTheme,
  })
  return (
    <>
      <Toolbar>
      <ButtonUndo onClick={onUndo} disabled={!canUndo} />
      <ButtonRedo onClick={onRedo} disabled={!canRedo} />
        {/*..import all other controls*/}
      </Toolbar>

      <CanvasGrid
        {...spreadsheetColors}
        enableTextOverflow
        stickyEditor={true}
        scale={scale}
        conditionalFormats={conditionalFormats}
        sheetId={activeSheetId}
        rowCount={rowCount}
        columnCount={columnCount}
        frozenColumnCount={frozenColumnCount}
        frozenRowCount={frozenRowCount}
        rowMetadata={rowMetadata}
        columnMetadata={columnMetadata}
        activeCell={activeCell}
        selections={selections}
        theme={theme}
        merges={merges}
        charts={charts}
        embeds={embeds}
        tables={tables}
        {/* Other methods from the hook */}
      />
    </>
  )
}

{/* Wrap your app in SpreadsheetProvider */}
export const Spreadsheet = () => (
  <SpreadsheetProvider>
    <App />
  </SpreadsheetProvider>
)

Spreadsheet features

Built for developers. Spreadsheet is rendered in HTML Canvas, giving you ~60fps rendering performance and ability to display millions of rows and columns without peformance impact.

Structured references

Structured references and Calculated columns

Create tables and reference cells using column names. Add calculated columns, which syncs with dynamic table cells

Real time formulas

Real-time formulas

Add named JavaScript functions as formulas, which can connect to real-time data sources.

Collaborate with users

Collaborative editing

Allow multiple users to work on a spreadsheet.

Agnostic of data strutures (CRDT/OT) or back-end stack. Use partykit, replicache etc.


  1. Conditional formatting with colors and color scales
  2. Data validation
  3. Frozen rows and columns
  4. Custom editors
  5. Custom tooltips
  6. Custom cell renderers
  7. Custom charts and embeds
  8. Cell styling
  9. Copy, Paste and Cut support
  10. Custom fonts
  11. Context menu
  12. Merge cells
  13. Show, Hide rows and columns
  14. Insert, Delete, Move rows and columns
  15. Dark mode
  16. Custom formula functions
  17. Protected ranges
  18. Named ranges
  19. Banded ranges
  20. Calculated columns
  21. Sorting filtering sheets and tables
  22. Easily integrate 3rd party APIs like OpenAI and other language models.