react-xel
v0.2.1
Published
Production-focused Next.js/React Excel exporter with native OOXML writer.
Maintainers
Readme
react-xel
Advanced Excel export toolkit for Next.js and React applications, with a native writer (no xlsx dependency).
Features
- Multi-sheet exports with per-sheet options
- Column formulas, number formats, and style hooks
- Header freeze, auto filter, row heights, merges, hidden columns
- Template archive validation guards (zip safety limits)
- Formula injection sanitization for user-provided strings
- Typed error classes for validation/security/writer failures
.xlsx,.csv, and.txtoutput- Buffer/blob/download modes for browser and server
- Next.js response helper (
toAttachmentResponse) - Delimited string exporter (
exportDelimited) - TanStack column mapper (
mapTanStackColumns) - Advanced adapters (
mapTanStackColumnsAdvanced,mapFormFieldsToColumns) - UI primitives:
ExcelExportButton,ExcelExportMenu,useExcelExportTask - Extension lifecycle hooks with deterministic priority ordering
- Headless UI mode + tokenized CSS variables for design systems
Installation
pnpm add react-xelQuick client export
"use client";
import { exportExcel } from "react-xel";
export function DownloadButton() {
const onClick = () => {
exportExcel({
fileName: "sales-report.xlsx",
rows: [
{ id: 1, customer: "Alice", total: 1200, createdAt: new Date() },
{ id: 2, customer: "Bob", total: 980, createdAt: new Date() }
],
sheetName: "Sales",
metadata: {
title: "Sales Report",
author: "Dashboard App"
}
});
};
return <button onClick={onClick}>Download Excel</button>;
}Advanced workbook (multi-sheet + formulas + styles)
import { exportExcel } from "react-xel";
exportExcel({
fileName: "analytics.xlsx",
sheets: [
{
sheetName: "Revenue",
rows: [
{ month: "Jan", gross: 15000, feeRate: 0.05 },
{ month: "Feb", gross: 22000, feeRate: 0.05 }
],
columns: [
{ header: "Month", key: "month", width: 14 },
{ header: "Gross", key: "gross", numberFormat: "#,##0.00" },
{ header: "Fee %", key: "feeRate", numberFormat: "0.00%" },
{
header: "Net",
formula: (_, rowIndex) => `B${rowIndex + 2}*(1-C${rowIndex + 2})`,
numberFormat: "#,##0.00"
}
],
autoFilter: true,
freezeHeaderRow: true,
headerStyle: { font: { bold: true } },
rowHeights: [24, 20, 20]
},
{
sheetName: "Users",
rows: [
{ plan: "Free", count: 2300 },
{ plan: "Pro", count: 410 }
],
columns: [
{ header: "Plan", key: "plan", width: 18 },
{ header: "Users", key: "count", width: 12, numberFormat: "#,##0" }
],
merges: ["A1:B1"]
}
],
metadata: {
title: "Business Snapshot",
subject: "Monthly KPIs",
author: "Ops Team",
company: "Acme Inc."
}
});Server route example (Next.js)
import { exportExcel, toAttachmentResponse } from "react-xel";
export async function GET(): Promise<Response> {
const buffer = exportExcel({
fileName: "server-report.xlsx",
rows: [
{ id: 1, status: "ok" },
{ id: 2, status: "queued" }
],
mode: "buffer"
});
if (!buffer) {
throw new Error("Expected buffer output.");
}
return toAttachmentResponse(buffer, "server-report.xlsx");
}Notes
- This version writes
.xlsxdirectly from generated OOXML + ZIP. - Date values are serialized as Excel date numbers (not ISO text).
templateBufferis validated for archive safety but full template mutation is not implemented yet.
UI package
Import from react-xel/ui.
ExcelExportButtonExcelExportMenuExcelExportPaneluseExcelExportTask
Import default styles once (for polished built-in look):
import "react-xel/styles.css";"use client";
import { ExcelExportButton } from "react-xel/ui";
export function UsersExportButton() {
return (
<ExcelExportButton
label="Export Users"
fileName="users.xlsx"
rows={[
{ id: 1, name: "Ada" },
{ id: 2, name: "Linus" }
]}
sheetName="Users"
/>
);
}Core API
exportExcel(options)createWorkbook(options)workbookToBuffer(workbook, bookType?)arrayBufferToBlob(buffer, mimeType?)toAttachmentResponse(buffer, fileName, mimeType?)exportDelimited({ rows, columns, delimiter })mapTanStackColumns(columns)mapTanStackColumnsAdvanced(columns)mapFormFieldsToColumns(fields)createNextRouteHandler(buildOptions)createExtension(extension)defineColumns(columns)defineSheets(sheets)definePreset(preset)defineExtension(extension)composeExportOptions(...presets)useExcelExport(defaults?)ValidationError,WriterError,TemplateError,SecurityError
Performance benchmarks
Generate baseline performance metrics:
pnpm benchmark:jsonResults are written to benchmarks/latest.json and include wall-clock and memory deltas.
Extension model
See enterprise extension lifecycle guarantees in docs/extension-model.md.
Error handling
import { ValidationError, exportExcel } from "react-xel";
try {
exportExcel({ fileName: "bad.ext", rows: [{ id: 1 }] });
} catch (error) {
if (error instanceof ValidationError) {
console.error(error.code, error.message);
}
}Testing
Run tests with:
pnpm testRun only UI component tests:
pnpm test:ui:runWatch mode:
pnpm test:watchVitest UI:
pnpm test:uiCoverage:
pnpm test:coverageUI testing guide
1) Automated UI tests
This package already includes component tests in tests/ui.test.tsx for:
ExcelExportButtonclick behaviorExcelExportMenurendering- disabled state behavior
Run them with:
pnpm test:ui:runInteractive runner:
pnpm test:ui2) Manual UI verification in a Next.js app
Create a client page/component using react-xel/ui:
"use client";
import { ExcelExportButton, ExcelExportMenu } from "react-xel/ui";
export default function Page() {
return (
<div style={{ display: "grid", gap: 12 }}>
<ExcelExportButton
label="Export Users"
fileName="users.xlsx"
rows={[{ id: 1, name: "Ada" }]}
/>
<ExcelExportMenu
items={[
{
id: "users",
label: "Users Report",
options: { fileName: "users.xlsx", rows: [{ id: 1, name: "Ada" }] }
},
{
id: "sales",
label: "Sales Report",
options: { fileName: "sales.xlsx", rows: [{ month: "Jan", total: 1200 }] }
}
]}
/>
</div>
);
}Manual checklist:
- Button click downloads
users.xlsx - Menu selection changes export payload
- Disabled menu/button cannot trigger export
- File opens in Excel/Sheets with expected headers and rows
Monorepo workspace
This repository is configured as a pnpm workspace with a documentation site (not only demos):
apps/docs: Next.js 16 documentation app using Fumadocs (MDX content, search, layouts). It consumesreact-xelviaworkspace:*. The site lives under/docs(the root URL redirects there).
Run the documentation UI:
pnpm install
pnpm dev:docsThen open http://localhost:3000 (or http://localhost:3000/docs directly).
License
MIT
