A JSX-based Excel generator for Node.js. Create, compose, and style Excel spreadsheets using familiar JSX syntax and Tailwind-style class namesβno React or browser required.
- JSX Syntax - Write Excel spreadsheets as declarative JSX components
- Tailwind-style Styling - Use utility classes like
bg-blue-500,font-bold,border - Custom Components -
<Workbook>,<Worksheet>,<Row>,<Cell>,<Column>,<Group>,<Image>,<Template> - Templates - Load and populate existing Excel templates
- Images - Embed images from files or base64 data
- Processors - Transform nodes during rendering (e.g., zebra striping)
- No React - Custom JSX runtime designed for Excel generation
bun add @workspace/excelwind/** @jsxImportSource @workspace/excelwind */
import { Workbook, Worksheet, Row, Cell } from "@workspace/excelwind";
import { render } from "@workspace/excelwind";
import { tailwindExcel } from "@workspace/excelwind";
const spreadsheet = (
<Workbook>
<Worksheet name="Sales">
<Row>
<Cell value="Product" style={tailwindExcel("font-bold bg-blue-600 text-white")} />
<Cell value="Revenue" style={tailwindExcel("font-bold bg-blue-600 text-white")} />
</Row>
<Row>
<Cell value="Widget Pro" />
<Cell value={15000} />
</Row>
</Worksheet>
</Workbook>
);
const workbook = await render(spreadsheet);
const buffer = await workbook.xlsx.writeBuffer();
await Bun.write("output.xlsx", buffer);Run all examples to see the full capabilities:
# Run all examples
bun run examples
# Or run individual examples
bun run example:basic # Basic workbook creation
bun run example:styling # Tailwind-style classes
bun run example:dynamic # Dynamic data generation
bun run example:processors # Custom row processors
bun run example:merged # Cell merging (colSpan/rowSpan)
bun run example:templates # Excel templates
bun run example:images # Embedded imagesOutput files are generated in examples/output/.
Use the tailwindExcel() utility to convert Tailwind-style classes to Excel styles:
import { tailwindExcel } from "@workspace/excelwind";
// Colors
tailwindExcel("bg-blue-500 text-white")
// Typography
tailwindExcel("font-bold text-lg text-center")
// Borders
tailwindExcel("border border-gray-300 border-thick")
// Alignment
tailwindExcel("text-left align-center")
// Combined
tailwindExcel("font-bold bg-indigo-600 text-white text-center border-b")| Category | Classes |
|---|---|
| Background | bg-{color}-{100-900} |
| Text Color | text-{color}-{100-900} |
| Font | font-bold, text-sm, text-lg, text-xl, text-2xl |
| Alignment | text-left, text-center, text-right, align-top, align-center, align-bottom |
| Borders | border, border-{t,r,b,l,x,y}, border-{color}-{shade}, border-thick |
| Text Wrap | text-nowrap |
Root container for the Excel file.
A sheet within the workbook.
<Worksheet name="Sheet1" properties={{ tabColor: { argb: "FF0000" } }}>
{/* rows and columns */}
</Worksheet>Define column properties.
<Column width={20} format='"$"#,##0.00' style={tailwindExcel("text-right")} />
<Column id="Dates" width={15} format="yyyy-mm-dd" />A row of cells.
<Row height={30}>
<Cell value="Hello" />
</Row>Individual cell with value and styling.
<Cell value="Text" />
<Cell value={12345} format='"$"#,##0.00' />
<Cell value={new Date()} />
<Cell formula="SUM(A1:A10)" />
<Cell value="Merged" colSpan={3} rowSpan={2} />Group cells/rows for shared styling or processing.
<Group style={tailwindExcel("bg-gray-100")} processor={zebraStripeProcessor}>
{rows.map(row => <Row>...</Row>)}
</Group>Embed images in cells.
<Image
src="./logo.png"
extension="png"
position={{ tl: { col: 0, row: 0 }, ext: { width: 100, height: 50 } }}
tooltip="Company Logo"
/>
<Image
buffer={base64String}
extension="png"
position={{ tl: { col: 0, row: 0 }, ext: { width: 64, height: 64 } }}
/>Load and populate Excel templates.
<Template
src="template.xlsx"
data={{
company: { name: "Acme Corp" },
rows: [{ item: "Widget", price: 100 }]
}}
/>Processors transform nodes during rendering. Useful for conditional styling:
import { Processor, AnyNode, ProcessorContext } from "@workspace/excelwind";
import { isRow, mergeDeep } from "@workspace/excelwind";
const zebraStripe: Processor = (node: AnyNode, ctx: ProcessorContext) => {
if (!isRow(node) || ctx.rowIndex === undefined) return node;
if (ctx.rowIndex % 2 === 1) {
return {
...node,
props: {
...node.props,
style: mergeDeep(node.props.style, {
fill: { type: "pattern", pattern: "solid", fgColor: { argb: "F3F4F6" } }
})
}
};
}
return node;
};
<Group processor={zebraStripe}>
{data.map(item => <Row>...</Row>)}
</Group>excelwind/
βββ src/
β βββ index.ts # Main exports
β βββ components.tsx # JSX components
β βββ renderRows.ts # Rendering engine
β βββ tailwind.ts # Tailwind class parser
β βββ types.ts # TypeScript types
β βββ utils.ts # Utility functions
β βββ jsx-runtime/ # Custom JSX runtime
βββ examples/
β βββ 01-basic.tsx
β βββ 02-styling.tsx
β βββ 03-dynamic-data.tsx
β βββ 04-processors.tsx
β βββ 05-merged-cells.tsx
β βββ 06-templates.tsx
β βββ 07-images.tsx
β βββ run-all.ts
β βββ assets/ # Template files and images
β βββ output/ # Generated Excel files
βββ package.json
MIT