Skip to content

prkgnt/react-excel-lite

Repository files navigation

react-excel-lite

GitHub repo npm package npm downloads MIT License PRs Welcome

A lightweight, Excel-like editable grid component for React.

Demo

react-excel-lite-demo

Features

  • Excel-like cell selection (click & drag)
  • Keyboard navigation (Arrow keys to move, Shift+Arrow to extend selection)
  • Copy/Paste support (Ctrl+C / Ctrl+V)
  • Auto Fill with arithmetic sequence detection (drag fill handle)
  • Grouped column headers with rowSpan support
  • Grouped row headers with rowSpan support
  • Click outside to clear selection
  • Double-click or type to edit cells
  • Expandable input field when editing (text overflow handling)
  • Keyboard shortcuts (Delete/Backspace to clear)
  • Styling-agnostic: Works with Tailwind CSS, CSS Modules, plain CSS, or any styling solution
  • Zero external dependencies

Installation

npm install react-excel-lite

Quick Start

import { useState } from "react";
import { ExcelGrid } from "react-excel-lite";

function App() {
  const [data, setData] = useState([
    ["100", "200", "300"],
    ["400", "500", "600"],
    ["700", "800", "900"],
  ]);

  return <ExcelGrid data={data} onChange={setData} />;
}

Props

Prop Type Required Description
data string[][] Yes 2D array of strings
onChange (data: string[][]) => void Yes Callback when data changes
rowHeaders HeaderGroup[] No Grouped row headers
colHeaders HeaderGroup[] No Grouped column headers
className string No CSS class for container
rowHeaderTitle string No Title for row header column
styles GridStyles No Style configuration object
cellStyles (coord: CellCoord) => string|undefined No Function to style individual cells

With Headers

import { useState } from "react";
import { ExcelGrid } from "react-excel-lite";
import type { HeaderGroup } from "react-excel-lite";

function App() {
  const [data, setData] = useState([
    ["100", "200", "300", "400"],
    ["500", "600", "700", "800"],
  ]);

  const colHeaders: HeaderGroup[] = [
    {
      label: "Q1",
      description: "First quarter",
      headers: [
        { key: "jan", label: "Jan", description: "January" },
        { key: "feb", label: "Feb", description: "February" },
      ],
    },
    {
      label: "Q2",
      description: "Second quarter",
      headers: [
        { key: "mar", label: "Mar", description: "March" },
        { key: "apr", label: "Apr", description: "April" },
      ],
    },
  ];

  const rowHeaders: HeaderGroup[] = [
    {
      label: "Products",
      description: "Product categories",
      headers: [
        { key: "prodA", label: "Product A", description: "Main product line" },
        { key: "prodB", label: "Product B", description: "Secondary product" },
      ],
    },
  ];

  return (
    <ExcelGrid
      data={data}
      onChange={setData}
      colHeaders={colHeaders}
      rowHeaders={rowHeaders}
      rowHeaderTitle="Category"
    />
  );
}

Simple Headers (Without Group Labels)

When all HeaderGroups have no label, the grid displays a single header row/column instead of two levels:

const colHeaders: HeaderGroup[] = [
  {
    // No label - single header row
    headers: [
      { key: "jan", label: "Jan" },
      { key: "feb", label: "Feb" },
    ],
  },
  {
    headers: [
      { key: "mar", label: "Mar" },
      { key: "apr", label: "Apr" },
    ],
  },
];

const rowHeaders: HeaderGroup[] = [
  {
    // No label - single header column
    headers: [
      { key: "row1", label: "Row 1" },
      { key: "row2", label: "Row 2" },
    ],
  },
];

<ExcelGrid
  data={data}
  onChange={setData}
  colHeaders={colHeaders}
  rowHeaders={rowHeaders}
/>;

If at least one group has a label, the grid shows the two-level layout (group labels + individual headers).

Styling

The component comes with sensible default styles built-in. You can customize styles using the styles prop with CSS class strings from any styling solution.

Default Styles

Out of the box, the grid has:

  • Light gray borders and headers
  • Blue selection highlight
  • Blue fill handle

Custom Styling with Tailwind CSS

import type { GridStyles } from "react-excel-lite";

const styles: GridStyles = {
  cell: "text-sm",
  selected: "bg-purple-100 ring-2 ring-inset ring-purple-500",
  fillTarget: "bg-purple-50",
  fillHandle: "bg-purple-500",
  colGroup: "bg-purple-100 text-purple-700",
  colHeader: "bg-purple-50",
  rowHeader: "bg-slate-200",
};

<ExcelGrid data={data} onChange={setData} styles={styles} />;

Custom Styling with CSS Modules

import styles from "./grid.module.css";
import type { GridStyles } from "react-excel-lite";

const gridStyles: GridStyles = {
  selected: styles.selectedCell,
  fillTarget: styles.fillTargetCell,
  fillHandle: styles.fillHandle,
};

<ExcelGrid data={data} onChange={setData} styles={gridStyles} />;

Custom Styling with Plain CSS

const styles: GridStyles = {
  selected: "my-selected-cell",
  fillTarget: "my-fill-target",
  fillHandle: "my-fill-handle",
};

<ExcelGrid data={data} onChange={setData} styles={styles} />;
/* styles.css */
.my-selected-cell {
  background-color: #f3e8ff;
  outline: 2px solid #a855f7;
  outline-offset: -2px;
}

.my-fill-target {
  background-color: #faf5ff;
}

.my-fill-handle {
  background-color: #a855f7;
}

GridStyles Interface

interface GridStyles {
  cell?: string;       // CSS class for data cells
  selected?: string;   // CSS class for selected cells (overrides default)
  fillTarget?: string; // CSS class for fill target cells (overrides default)
  fillHandle?: string; // CSS class for fill handle (overrides default)
  colGroup?: string;   // CSS class for column group headers
  colHeader?: string;  // CSS class for individual column headers
  rowHeader?: string;  // CSS class for row headers
}

Styling Individual Headers

Style individual column headers and groups:

const colHeaders: HeaderGroup[] = [
  {
    label: "Revenue",
    className: "bg-green-100 text-green-700",
    headers: [
      { key: "q1r", label: "Q1", className: "bg-green-50" },
      { key: "q2r", label: "Q2", className: "bg-green-50" },
    ],
  },
];

Style individual row headers:

const rowHeaders: HeaderGroup[] = [
  {
    label: "Regions",
    className: "bg-slate-700 text-white",
    headers: [
      { key: "regionA", label: "Region A", className: "bg-slate-600 text-white" },
      { key: "regionB", label: "Region B", className: "bg-slate-500 text-white" },
    ],
  },
];

Styling Individual Cells

Use the cellStyles prop to apply styles to specific cells based on their coordinates:

import { useCallback } from "react";
import type { CellCoord } from "react-excel-lite";

function App() {
  const [data, setData] = useState([
    ["100", "200", "300"],
    ["400", "500", "600"],
    ["700", "800", "900"],
  ]);

  // Memoize to prevent unnecessary re-renders
  const cellStyles = useCallback((coord: CellCoord) => {
    // Highlight first row
    if (coord.row === 0) return "bg-yellow-100";
    // Highlight specific cell
    if (coord.row === 1 && coord.col === 1) return "bg-red-100 font-bold";
    // Highlight cells with negative values (check data)
    return undefined;
  }, []);

  return <ExcelGrid data={data} onChange={setData} cellStyles={cellStyles} />;
}

Common use cases:

  • Highlight header rows or columns
  • Show validation errors (e.g., red background for invalid cells)
  • Conditional formatting based on cell values
  • Alternating row colors
// Alternating row colors
const cellStyles = useCallback((coord: CellCoord) => {
  return coord.row % 2 === 0 ? "bg-gray-50" : "bg-white";
}, []);

// Value-based styling (check data in callback)
const cellStyles = useCallback((coord: CellCoord) => {
  const value = Number(data[coord.row]?.[coord.col]);
  if (value < 0) return "bg-red-100 text-red-700";
  if (value > 1000) return "bg-green-100 text-green-700";
  return undefined;
}, [data]);

Auto Fill (Arithmetic Sequence)

Select cells with a numeric pattern and drag the fill handle to auto-fill:

  • 1, 2, 3 → drag down → 4, 5, 6, 7, ...
  • 100, 200, 300 → drag down → 400, 500, 600, ...
  • 10, 8, 6 → drag down → 4, 2, 0, -2, ...
  • Text values → repeats the pattern

Keyboard Shortcuts

Shortcut Action
Arrow Keys Move selection
Shift + Arrow Keys Extend selection range
Enter Enter edit mode (select all text)
Any character Enter edit mode and start typing
Escape Exit edit mode
Ctrl+C / Cmd+C Copy selected cells
Ctrl+V / Cmd+V Paste from clipboard
Delete / Backspace Clear selected cells

Exports

Components

  • ExcelGrid - Main grid component
  • GridCell - Individual cell component

Hooks

  • useGridSelection - Cell selection logic
  • useGridClipboard - Copy/paste and keyboard navigation logic
  • useGridDragFill - Fill handle logic

Utilities

  • cn - Classname merge utility
  • coordToKey - Convert coordinate to string key
  • keyToCoord - Convert string key to coordinate
  • getCellsInRange - Get all cells in a range
  • isCellInRange - Check if cell is in range
  • parseTSV - Parse TSV string to 2D array
  • toTSV - Convert 2D array to TSV string
  • normalizeRange - Normalize selection range
  • getFillTargetCells - Get fill target cells

Types

interface CellCoord {
  row: number;
  col: number;
}

interface SelectionRange {
  start: CellCoord | null;
  end: CellCoord | null;
}

interface Header {
  key: string;
  label: string;
  description?: string;
  className?: string;
}

interface HeaderGroup {
  label?: string;
  headers: Header[];
  description?: string;
  className?: string;
}

interface GridStyles {
  cell?: string;
  selected?: string;
  fillTarget?: string;
  fillHandle?: string;
  colGroup?: string;
  colHeader?: string;
  rowHeader?: string;
}

interface ExcelGridProps {
  data: string[][];
  onChange: (data: string[][]) => void;
  rowHeaders?: HeaderGroup[];
  colHeaders?: HeaderGroup[];
  className?: string;
  rowHeaderTitle?: string;
  styles?: GridStyles;
  cellStyles?: (coord: CellCoord) => string | undefined;
}

License

MIT License © 2025 prkgnt

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •  

Languages