Overview — @helix-ui/slides
@helix-ui/slides is a separate package in the helix-ui 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 @helix-ui/slides
# Optional — only needed when you actually call the exporter.pnpm add pptxgenjsThe pptxgenjs library is a lazy peer dependency. The base @helix-ui/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 '@helix-ui/slides';import '@helix-ui/slides/styles.css';
function Pitch() { return ( <Deck size="16:9" exportFileName="pitch.pptx"> <Slide layout="title"> <Title>helix-ui 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 helix-ui</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 helix-ui CSS variables, so any DNA theme just
flows through. During PPTX export, any token reference ('brand',
'--helix-ui-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