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.pptxexactly.
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
pnpm add @helixui/slides
# Optional — only needed when you actually call the exporter.pnpm add pptxgenjsThe 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 handlerawait exportToPptx(<Pitch />); // → pitch.pptxHow 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.
| Component | PPTX equivalent | Notes |
|---|---|---|
<Slide> | sld | One slide per element |
<Frame> | shape group bounds | Children inherit x / y / w / h |
<Title> / <Subtitle> | sp text body | Layout placeholders kick in for blank decks |
<Heading> | sp text body | level={1..4} controls the size scale |
<SlideText> | sp text body | Free-form runs |
<Bullets> + <Bullet> | sp text body w/ a:buChar/a:buAutoNum | Per-item indent levels |
<SlideShape> | sp prstGeom | Twenty preset shapes mapped 1:1 |
<SlideImage> | pic | Inlined as base64 in the browser |
<SlideTable> | graphicFrame tbl | Striped rows, colWidths honored |
<SlideChart> | graphicFrame chart | Native PowerPoint chart, data-bound |
<Notes> | notesSld | Speaker 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) andtransform: 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— previousHome/End— first / last
Pair it with <DeckThumbnails> for a navigable strip.
What’s next
- Components — every primitive at a glance
- Authoring guide — patterns for real decks
- Exporting — async export, file metadata, blob output, fonts, image inlining