@n4ze3m/react2pptx
v0.7.0
Published
Export a React Router slide deck (Vite app) to an editable .pptx file. CLI + programmatic API.
Downloads
1,154
Maintainers
Readme
react2pptx
Export a React Router slide deck (built with Vite) to an editable PowerPoint file.
The exporter walks the DOM and emits native PPTX text boxes, shapes, and images — not screenshots. Recipients can open the result in PowerPoint or Keynote and edit text, restyle shapes, copy slides, and re-flow content.
Installation
npm install -D react2pptx
# or
pnpm add -D react2pptxEvery runtime dependency (pptxgenjs, playwright-core, yaml) is pulled in automatically. The only thing you need on your machine is a Chrome/Chromium/Edge install — react2pptx finds it for you on Linux, macOS, and Windows.
CLI
react2pptx [projectDir] [-o output.pptx]Examples:
react2pptx # export ./ to ./deck.pptx
react2pptx . -o presentation.pptx
react2pptx ./apps/marketing -o out.pptx
react2pptx --url http://localhost:5173 -o deck.pptx # capture against a running serverRun react2pptx --help for the full flag list.
Configuration via slide.yml
Drop a slide.yml in your project root to customize the pipeline (e.g. for Next.js, Astro, or a non-default port). Every field is optional:
enable: true # gate for `react2pptx validate`; set false to disable
build: vite build
preview: vite preview --port {port} --host 127.0.0.1 --strictPort
routePattern: /slide/{i}?export=1
layout: LAYOUT_16x9
output: deck.pptx
timeoutSec: 30See slide.example.yml shipped with this package for the annotated schema.
CLI flags take precedence over slide.yml; slide.yml takes precedence over built-in defaults.
Gating validate with enable
react2pptx validate only runs when enable: true (the default). If your project ships enable: false, the command refuses with:
Error: `react2pptx validate` is disabled. Set `enable: true` in slide.yml to enable it.Other commands (the default export) ignore this field.
Project conventions
The exporter discovers slides by hitting /slide/0, /slide/1, ... and looks for two DOM signals on each page:
[data-slide-root]— the element it should measure and walk.[data-total="N"]on the same element — the total slide count.
For slides that load data asynchronously, also set [data-slide-ready="true"] once the slide is fully rendered. The exporter waits up to timeoutSec seconds for this attribute to appear.
A minimal slide host looks like this:
// src/components/SlideDeck.tsx
import { useParams } from 'react-router';
export function SlideDeck({ slides }: { slides: React.ComponentType[] }) {
const { id = '0' } = useParams();
const index = parseInt(id, 10);
const Slide = slides[index];
return (
<div data-slide-root data-total={slides.length} style={{ width: 960, height: 540 }}>
<Slide />
</div>
);
}Programmatic API
import pptxgen from 'pptxgenjs';
import { react2pptx, exportJob } from 'react2pptx';
// Option A: full pipeline (build + preview + capture)
await exportJob('./my-slide-project', { output: 'deck.pptx' });
// Option B: capture against an already-running server, with a custom
// `onSlide` hook for injecting native PowerPoint charts into placeholders.
const pres = new pptxgen();
pres.layout = 'LAYOUT_16x9';
await react2pptx('http://localhost:5173', pres, {
onSlide({ slide, placeholders, index }) {
const chartHere = placeholders.find((p) => p.id === 'revenue-chart');
if (chartHere) {
slide.addChart(pres.charts.BAR, [/* ... */], chartHere);
}
},
});
await pres.writeFile({ fileName: 'deck.pptx' });Fidelity
This exporter is editable, not pixel-perfect — see the design notes shipped alongside the source. The TL;DR:
- Use fonts that ship with Office (Calibri, Aptos, Arial, Georgia, Cambria, Consolas) for predictable rendering on every machine.
- Leave 4–8 px of slack in tight text boxes; PowerPoint's text shaper differs slightly from Chrome's.
- Wrap text in
<p>/<h1>–<h6>/<ul>/<ol>; bare text in<div>is rejected. - Box styling (background/border/shadow/radius) goes on
<div>; text styling goes on the text element. - CSS gradients, filters, blend modes,
clip-path, and gradient text are auto-rasterized: those elements are screenshotted and embedded as PNGs at the same position, so the slide still looks right — they just aren't editable as text/shape in PowerPoint.
The CLI will print every validation issue it finds, with the slide index and the source of the problem, so you can fix them at authoring time instead of debugging a broken .pptx.
Bitmap fallback (data-slide-rasterize)
If you have a subtree with CSS effects we can't translate semantically — or you simply want to flatten a section to a PNG (the way Gamma does) — add data-slide-rasterize to any element:
<div data-slide-rasterize="hero">
<ComplicatedHeroWithBlendModesAndGradients />
</div>The exporter takes an element-scoped screenshot, replaces the subtree with a single <img> at the same position, and embeds that PNG in the slide. Anything outside the marked subtree (titles, body copy, charts) stays as native editable PowerPoint objects.
The same fallback kicks in automatically when the walker sees filter, backdrop-filter, mix-blend-mode, clip-path, mask-image, gradient backgrounds (on non-root elements), or gradient text — so most decks Just Work without any annotation.
Requirements
- Node.js 20 or newer
- Chrome, Chromium, or Edge installed locally (or pointed to via
CHROME_PATH) - A Vite project (or any other static site you can serve) with the slide conventions above
License
MIT
