Skip to content

Markdown to .pptx in one React tree

May 25, 2026

Every team I’ve worked on has the same broken loop. Product writes a spec in Notion. Design pitches it as a Figma deck. Engineering builds it as a web page. Sales presents it from PowerPoint, which someone manually rebuilt from the Figma deck. Marketing exports the same content to PDF.

Five surfaces. Five copies. Five places where the brand can drift.

helixui’s @helixui/slides and @helixui/document packages exist because this is solvable. Same React tree, multiple outputs.

Same source, three formats

import { Deck, Slide, Title, Bullet, Bullets, exportToPptx } from '@helixui/slides';
function Pitch() {
return (
<Deck size="16x9">
<Slide layout="title">
<Title>helixui</Title>
<Bullet>Themes are genomes.</Bullet>
</Slide>
<Slide layout="content">
<Title>Why this matters</Title>
<Bullets>
<Bullet>Tokens are JSON, not code.</Bullet>
<Bullet>Every component documents itself.</Bullet>
<Bullet>22-gene DNA you can breed.</Bullet>
</Bullets>
</Slide>
</Deck>
);
}
// Render to DOM: <Pitch />
// Export to PPTX: await exportToPptx(<Pitch />);
// Email as a blob: await deckToPptxBlob(<Pitch />);

pptxgenjs is loaded lazily — your web bundle doesn’t pay for the export until someone hits the button.

Same story for documents

import { Document, Heading, Paragraph, exportToDocx } from '@helixui/document';
const Report = () => (
<Document>
<Heading level={1}>Q3 Report</Heading>
<Paragraph>Revenue grew 18%...</Paragraph>
</Document>
);
await exportToDocx(<Report />); // .docx

docx (the library) is also lazy-loaded.

The new Markdown studio

We just shipped a Markdown studio at /showcase/markdown. Paste markdown — readme, changelog, spec.md, anything — into the editor. The page renders it with helixui tokens. Two buttons in the header:

  • 📥 .pptx — heading-1 / heading-2 become slide titles; bullets, paragraphs, quotes, code, and tables all map to their PPTX equivalents.
  • 📥 .docx — full markdown → Word, with code blocks, tables, blockquotes, task lists.

Same input. Two pro outputs. Zero conversion errors.

The escape hatch

For things the typed surface doesn’t cover (video, audio, raw shapes), exportToPptx accepts onPres and onSlide hooks that give you the underlying pptxgenjs instance:

await exportToPptx(<Pitch />, {
onSlide: ({ slide, index, element }) => {
if (element.props.video) {
slide.addMedia({
type: 'video',
path: element.props.video,
x: 0.5, y: 0.5, w: 9, h: 5,
});
}
},
});

Most users never touch this. It’s there for the 5% of cases the declarative surface doesn’t reach.

Why this is more than a parlor trick

A team that adopts helixui gets:

  • One brand layer. Tokens are tokens, regardless of surface.
  • One review loop. Engineering reviews the React tree. The PPTX is byproduct.
  • One deployment. When the marketing copy changes, the deck changes. No “send me the latest PPT” Slack thread.

Treat documents and decks as React, the same way you treat the web UI. The cost of consistency drops to zero.

What we don’t support (yet)

Documented in /docs/slides/exporting:

  • Slide transitions / animations (out of scope — decks are durable artifacts, not playback engines).
  • Speaker timings.
  • Complex mid-paragraph color shifts in a single text run.

Use the escape hatches if you need them.

Try it

Terminal window
npm create helixui-app pitch --template marketing

Open src/App.tsx. Wrap a chunk in a <Deck>. Add an “Export” button. Send the .pptx to your boss.