Skip to content

Overview — @helixui/slides

@helixui/slides is a separate package in the helixui monorepo that lets you build presentations the same way you build the rest of your UI — as React components.

The mental model is simple:

A <Deck> is a container of <Slide>s. Each slide holds positioned children. Coordinates are in inches — the same unit PowerPoint uses — so what you see on screen lands in the .pptx exactly.

Live demo: /showcase/slides — build, navigate, and press ⬇ Export .pptx. Open the file in PowerPoint, Keynote, or Google Slides and it’ll be a real, editable presentation.

Install

Terminal window
pnpm add @helixui/slides
# Optional — only needed when you actually call the exporter.
pnpm add pptxgenjs

The pptxgenjs library is a lazy peer dependency. The base @helixui/slides bundle does not import it until you call exportToPptx(). If you only want the web preview, you don’t need to install it at all.

A 30-second deck

import {
Deck, Slide, Title, Subtitle, Bullets, Bullet, exportToPptx,
} from '@helixui/slides';
import '@helixui/slides/styles.css';
function Pitch() {
return (
<Deck size="16:9" exportFileName="pitch.pptx">
<Slide layout="title">
<Title>helixui slides</Title>
<Subtitle>tokens · components · pptx export</Subtitle>
</Slide>
<Slide layout="content">
<Title>Why this exists</Title>
<Bullets type="check">
<Bullet>Same tokens as the rest of helixui</Bullet>
<Bullet>Diffable, reviewable, programmatic</Bullet>
<Bullet>Native PPTX export — keeps charts editable</Bullet>
</Bullets>
</Slide>
</Deck>
);
}
// in a button handler
await exportToPptx(<Pitch />); // → pitch.pptx

How it maps to PPTX

Every primitive corresponds 1:1 to a PPTX construct so the export is predictable and lossless within the supported feature set.

ComponentPPTX equivalentNotes
<Slide>sldOne slide per element
<Frame>shape group boundsChildren inherit x / y / w / h
<Title> / <Subtitle>sp text bodyLayout placeholders kick in for blank decks
<Heading>sp text bodylevel={1..4} controls the size scale
<SlideText>sp text bodyFree-form runs
<Bullets> + <Bullet>sp text body w/ a:buChar/a:buAutoNumPer-item indent levels
<SlideShape>sp prstGeomTwenty preset shapes mapped 1:1
<SlideImage>picInlined as base64 in the browser
<SlideTable>graphicFrame tblStriped rows, colWidths honored
<SlideChart>graphicFrame chartNative PowerPoint chart, data-bound
<Notes>notesSldSpeaker notes pane

Why inches?

Every coordinate (x, y, w, h, radius, padding) is a number in inches. PowerPoint uses inches internally (well, EMUs — 914,400 per inch), and so does pptxgenjs. Using inches in the source means:

  • No coordinate translation. What you write is what gets exported.
  • Consistent rendering. The DOM renderer scales inches to CSS pixels (1in = 96px) and transform: scale()s the slide canvas to fit any container. A 1-inch shape is always 1 inch — at 100% zoom or 30%.
  • Print-ready math. Slide dimensions match the standard sizes: 16:9 → 13.333 × 7.5 in, 4:3 → 10 × 7.5 in.

Layouts vs. explicit Frames

Two ways to position content:

{/* 1. Use a layout — placeholders position semantic primitives */}
<Slide layout="content">
<Title>Click here.</Title>
<Bullets><Bullet>Anywhere.</Bullet></Bullets>
</Slide>
{/* 2. Use Frames — absolute positioning in inches */}
<Slide>
<Frame x={0.5} y={0.5} w={6} h={1}>
<Title>Pixel-perfect.</Title>
</Frame>
</Slide>

Both compose. A <Title> inside a <Frame> uses the frame’s bounds; a <Title> outside a frame falls into the layout’s title placeholder.

Theming

<Deck> accepts a theme prop with colors, fonts, and default font sizes. The defaults pull values from helixui CSS variables, so any DNA theme just flows through. During PPTX export, any token reference ('brand', '--helixui-color-...', oklch(...), etc.) is resolved to a concrete hex via a hidden canvas — your decks export faithfully under any theme.

<Deck
theme={{
colors: {
brand: 'oklch(0.62 0.18 264)',
ink: '#0b0d12',
paper: '#fafafa',
},
fonts: { heading: '"Geist", system-ui, sans-serif' },
titleFontSize: 56,
}}
>
</Deck>

Speaker mode

<Deck mode="single" controls> shows one slide at a time and binds:

  • / Space / PageDown — next
  • / PageUp — previous
  • Home / End — first / last

Pair it with <DeckThumbnails> for a navigable strip.

What’s next