HTML slide presentation generator with PPTX export, self-healing validation, brand injection, and a streaming React viewer.
Generate slide decks from HTML, preview them live with streaming support, and export to editable PowerPoint files -- all client-side.
- HTML to PPTX -- Convert HTML slides to editable PowerPoint files with native charts, preserved layouts, and editable text
- Self-healing validator -- Automatically fixes missing dimensions, overflow, flex layout, chart types, and other common LLM output issues
- Streaming React viewer -- Progressive rendering with skeleton loading, slide counter, and auto-scroll during generation
- Brand injection -- Automatically inject logos, headers, footers, and copyright into every slide via theme configuration
- Customizable themes -- Register themes with colors, fonts, chart palettes, table styles, and brand assets
- LLM prompt generation -- Theme-aware system prompt fragments for teaching LLMs to create slide presentations
- Native Chart.js support -- Chart.js data in slides is converted to native, editable PowerPoint charts
- Material Icons -- Automatic rasterization of Material Icons for PPTX compatibility
npm install presentximport { SlideViewer } from 'presentx/react';
import 'presentx/styles';
function App() {
return (
<SlideViewer
html={slidesHtml}
isStreaming={false}
theme="default"
/>
);
}import { convertSlidesToPptx, validatePresentation } from 'presentx';
// Validate and self-heal the HTML
const report = validatePresentation(slidesHtml);
// Convert to PowerPoint
const blob = await convertSlidesToPptx(report.fixedHtml, 'My Presentation');
// Download
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'presentation.pptx';
a.click();const { getSlideInstructions } = require('presentx/prompts');
const prompt = getSlideInstructions({ theme: 'my-brand' });
// Returns a system prompt fragment that teaches the LLM how to create slides| Import | Environment | Contents |
|---|---|---|
presentx |
Browser | Converter, validator, themes, brand injection, utilities |
presentx/react |
Browser + React | SlideViewer, SlideCounter components |
presentx/prompts |
Node.js / Browser | LLM instruction generation, routing rules, theme registry |
presentx/styles |
Browser | CSS for the React viewer |
presentx is designed to work with any LLM-powered chat platform. The library provides system prompt fragments that teach models how to generate slide HTML, a streaming viewer for real-time preview, and a PPTX converter for download -- all decoupled so you can adopt them independently.
On your API server, use presentx/prompts to generate a system prompt fragment. This teaches the LLM the slide HTML format, available components (charts, tables, icons, images), layout rules, and your brand palette.
// server/prompts.js
const { getSlideInstructions, registerTheme } = require('presentx/prompts');
// Optional: register a custom brand theme
registerTheme({
name: 'my-brand',
colors: {
primary: '#1a1a2e',
accent: '#e94560',
text: '#333333',
textLight: '#888888',
bgDark: '#1a1a2e',
bgLight: '#f5f5f5',
},
fonts: { heading: 'Montserrat', body: 'Inter' },
chartPalette: ['#1a1a2e', '#e94560', '#0f3460', '#16213e', '#533483'],
});
// Generate the slide instructions
const slidePrompt = getSlideInstructions({
theme: 'my-brand',
// Optional: add platform-specific instructions on top
extraInstructions: '## Platform Rules:\n- Use websearch for images\n- No red in charts',
});
// Append to your system prompt
const systemPrompt = baseSystemPrompt + '\n\n' + slidePrompt;The prompt includes:
- Slide frame templates (960x540px body slides, title/closing slides)
- Font scale, layout patterns (KPIs, timelines, grids, charts)
- Chart.js
<canvas>+<script class="chart-data">syntax - Table styling with inline styles for PPTX compatibility
- Brand palette, fonts, and branding notices from your theme
- Hard rules (no truncation, complete tags, HTTPS images, etc.)
When the LLM responds with slide content (using your artifact syntax or a marker), render it with the SlideViewer:
// client/SlideArtifact.tsx
import { SlideViewer } from 'presentx/react';
import { registerTheme } from 'presentx';
import 'presentx/styles';
// Register the same theme on the client (for brand injection in the viewer)
registerTheme({
name: 'my-brand',
colors: { /* same as server */ },
fonts: { /* same as server */ },
chartPalette: [ /* same as server */ ],
brand: {
logo: '<svg>...</svg>', // Inline SVG or image URL
logoDark: '<svg>...</svg>', // Optional: for dark slide backgrounds
logoIcon: '<svg>...</svg>', // Optional: small icon for body slide headers
name: 'My Company',
header: {
enabled: true,
content: 'icon', // 'logo' | 'icon' | 'name'
position: 'right',
height: 36,
},
footer: {
enabled: true,
showLogo: true,
logoPosition: 'right',
copyright: '© 2025 My Company',
copyrightPosition: 'left',
height: 32,
},
},
});
function SlideArtifact({ html, isStreaming }: { html: string; isStreaming: boolean }) {
return (
<SlideViewer
html={html}
isStreaming={isStreaming}
theme="my-brand"
/>
);
}The viewer handles:
- Streaming: progressively renders slides as the LLM generates HTML tokens
- Validation: auto-fixes malformed slides (missing dimensions, broken tags)
- Brand injection: overlays logo/header/footer from the theme config
- Iframe sandbox: slides render in a sandboxed iframe with Tailwind, Google Fonts, Material Icons, and Chart.js pre-loaded
Add a download button that converts the viewed slides to an editable PowerPoint file:
import { convertSlidesToPptx, validatePresentation, getTheme, injectBrand } from 'presentx';
async function downloadPptx(html: string, title: string) {
// 1. Validate and self-heal
const report = validatePresentation(html);
// 2. Inject brand elements (logo, header, footer)
const theme = getTheme('my-brand');
const branded = injectBrand(report.fixedHtml, theme);
// 3. Convert to PPTX
const blob = await convertSlidesToPptx(branded, title, { theme: 'my-brand' });
// 4. Trigger download
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${title}.pptx`;
a.click();
URL.revokeObjectURL(url);
}The PPTX converter:
- Renders slides in a hidden iframe to capture exact layout via
getBoundingClientRect() - Converts Chart.js canvases to native editable PPTX charts
- Rasterizes SVG logos and Material Icons to embedded PNGs
- Embeds fonts for cross-platform fidelity
- Preserves table styles, borders, and zebra striping
LLM generates HTML slides
|
v
getSlideInstructions() <-- Server: teaches LLM the slide format
|
v
validatePresentation() <-- Client: self-heals malformed output
|
v
injectBrand() <-- Client: adds logo/header/footer from theme
|
+---> SlideViewer <-- Client: live preview (streaming or static)
|
+---> convertSlidesToPptx() <-- Client: export to editable PowerPoint
|
v
PptxGenJS engine <-- DOM traversal + native charts + font embedding
default-- Neutral palette (navy primary, light blue accent, Arial fonts)
import { registerTheme } from 'presentx';
registerTheme({
name: 'my-brand',
colors: {
primary: '#1a1a2e',
accent: '#e94560',
text: '#333333',
textLight: '#888888',
bgDark: '#1a1a2e',
bgLight: '#f5f5f5',
},
fonts: {
heading: 'Montserrat',
body: 'Inter',
},
chartPalette: ['#1a1a2e', '#e94560', '#0f3460', '#16213e', '#533483'],
// Optional: additional named colors for the LLM prompt
extendedColors: {
borders: '#cccccc',
'subtle bg': '#e8e8e8',
},
// Optional: table styling
tableStyle: {
headerBg: '#1a1a2e',
headerText: '#ffffff',
headerTextTransform: 'uppercase',
headerFontWeight: '700',
evenRowBg: '#ffffff',
oddRowBg: '#f5f5f5',
rowText: '#333333',
rowBorder: true,
rowBorderColor: 'rgba(0,0,0,0.08)',
},
});Add a brand property to your theme to auto-inject logos, headers, and footers on every slide:
registerTheme({
name: 'my-brand',
colors: { /* ... */ },
fonts: { /* ... */ },
chartPalette: [ /* ... */ ],
brand: {
logo: '<svg>...</svg>', // Full wordmark (light bg)
logoDark: '<svg>...</svg>', // Full wordmark (dark bg)
logoIcon: '<svg>...</svg>', // Small icon mark (light bg)
logoIconDark: '<svg>...</svg>', // Small icon mark (dark bg)
name: 'My Company',
header: {
enabled: true, // Show header bar on body slides
content: 'icon', // 'logo' | 'icon' | 'name'
position: 'right', // 'left' | 'right'
height: 36,
},
footer: {
enabled: true, // Show footer bar
showLogo: true,
logoPosition: 'right',
copyright: '© 2025 My Company',
copyrightPosition: 'left',
height: 32,
showBorder: false,
},
},
});Three slide types get different brand treatment:
- Title slide (first): Full wordmark logo at top-left + copyright footer
- Closing slide (last): Same as title slide
- Body slides (middle): Icon in header bar + full footer with logo and copyright
The LLM prompt automatically includes branding notices so the model knows not to manually add logos.
Slides use Tailwind CSS classes inside 960x540px containers:
<div class="slide w-[960px] h-[540px] overflow-hidden relative flex flex-col pt-[48px] pb-[44px] px-[40px]">
<div class="shrink-0 pb-2 mb-3 border-b border-black/[0.06]">
<h2 class="text-[28px] font-bold leading-tight">Slide Title</h2>
</div>
<div class="flex-grow min-h-0 flex flex-col">
<!-- Body content fills remaining space -->
</div>
</div>Chart.js data embedded as JSON inside the slide:
<div class="slide w-[960px] h-[540px] overflow-hidden relative flex flex-col">
<div class="flex-grow">
<canvas data-chart-type="bar"></canvas>
</div>
<script type="application/json" class="chart-data">
{"labels":["Q1","Q2","Q3","Q4"],"datasets":[{"label":"Revenue","data":[100,200,300,400]}]}
</script>
</div>Charts render as native editable charts in the exported PPTX.
| Export | Description |
|---|---|
convertSlidesToPptx(html, title, options?) |
Convert HTML slides to PPTX blob |
validatePresentation(html) |
Validate and self-heal slide HTML |
registerTheme(theme) |
Register a custom theme |
getTheme(name?) |
Get a theme by name (default if omitted) |
listThemes() |
List registered theme names |
injectBrand(html, theme) |
Inject brand elements into slide HTML |
getBrandCss(theme) |
Get brand CSS rules for a theme |
countCompleteSlides(html) |
Count completed slides in HTML |
extractCompleteSlides(html) |
Extract only complete slide divs |
hasSlideContent(html) |
Check if HTML contains slides |
| Export | Description |
|---|---|
SlideViewer |
Streaming slide viewer component (props: html, isStreaming, theme, className, onValidationReport) |
SlideCounter |
Slide count indicator component |
| Export | Description |
|---|---|
getSlideInstructions(options?) |
Generate LLM system prompt for slide creation |
getRoutingRules() |
Get artifact routing table entry |
getArtifactTypeDescription() |
Get artifact type description string |
registerTheme(theme) |
Register a theme (also available from main entry) |
getTheme(name?) |
Get a theme (also available from main entry) |
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
Varun Muppidi -- GitHub