React components and hooks
@runstamp/react is an SSR-safe application layer for Runstamp decks. Browser exports contain no Node.js imports; the file renderer lives in the explicit @runstamp/react/server entry point.
Install
pnpm add @runstamp/react @runstamp/pptx react react-dom
Import the default stylesheet once in your application entry point:
import "@runstamp/react/styles.css";
Complete viewer
This Client Component renders a controlled viewer, thumbnail rail, navigation/download toolbar, and an honest fidelity state.
"use client";
import {
DeckProvider,
DeckThumbnails,
DeckToolbar,
DeckViewer,
useDeck,
type DeclarativeDocument,
} from "@runstamp/react";
import "@runstamp/react/styles.css";
const document: DeclarativeDocument = {
title: "Q3 operating review",
slides: [
{
layout: "title",
title: "Momentum with a clearer path to scale",
subtitle: "Q3 operating review",
},
{
layout: "kpi-row",
title: "The business at a glance",
metrics: [
{ label: "ARR", value: "$8.4M", delta: "+31% YoY", trend: "up" },
{ label: "NRR", value: "118%", delta: "+6 pts", trend: "up" },
],
},
],
};
export default function DeckWorkspace() {
const deck = useDeck(document);
return (
<DeckProvider deck={deck}>
<div className="deck-workspace">
<DeckThumbnails deck={deck} ariaLabel="Review slides" />
<main>
<DeckToolbar
deck={deck}
fileName="q3-operating-review"
pptx="/api/decks/q3-operating-review.pptx"
fidelity={{ status: "unverified", message: "Not yet Office validated" }}
/>
<DeckViewer deck={deck} ariaLabel="Q3 operating review" />
</main>
</div>
</DeckProvider>
);
}
The pptx and pdf toolbar props accept a URL, Blob, Uint8Array, or an async function returning one. A button stays disabled when its source is omitted.
DeckProvider
Provides one DeckController to headless descendants. Its deck prop accepts a DeckDocument or an existing controller. Components may receive deck directly or resolve it from the nearest provider.
DeckViewer
Renders the active slide on a responsive 16:9 canvas.
| Prop | Type | Default |
|---|---|---|
deck | DeckDocument | DeckController | Nearest provider |
theme | "light" | "dark" | DeckTheme | "light" |
renderSlide | (state: DeckRenderState) => ReactNode | Built-in renderer |
ariaLabel | string | "Presentation slide" |
className, style | React styling props | — |
DeckThumbnails
Renders every slide as a selectable thumbnail. renderThumbnail(state) replaces the built-in miniature while retaining navigation behavior and accessible labels.
DeckToolbar
Renders previous/next navigation, zero-padded page position, optional PPTX/PDF downloads, and an optional fidelity result.
| Prop | Type | Meaning |
|---|---|---|
fileName | string | Base download name; defaults to presentation. |
pptx, pdf | DownloadSource | URL, bytes, blob, or async producer. |
fidelity | FidelityStatus | FidelityResult | Real validation result shown beside downloads. |
FidelityBadge
import { FidelityBadge } from "@runstamp/react";
export default function ValidationStatus() {
return (
<FidelityBadge
status={{
status: "passed",
platform: "PowerPoint for Windows",
validatedAt: "2026-08-10T00:00:00.000Z",
}}
/>
);
}
Statuses are passed, failed, pending, and unverified. Only use passed for a real validation result. The badge sets role="status"; pending results also use a polite live region.
useDeck(source?, options?)
Creates or reads a DeckController. initialSlide is zero-based and clamped to the document bounds.
The controller exposes document, currentSlide, slideCount, canPrevious, canNext, setCurrentSlide(index), previous(), next(), first(), and last().
useDeckRender(source?, slideIndex?)
Returns a markup-agnostic DeckRenderState for the active or requested slide:
document,slide,slideIndex, andslideCount- the resolved slide
title modelwith normalized prose, bullets, KPIs, chart data, or AST nodes
Use it to own the markup while retaining Runstamp navigation state:
import { DeckProvider, useDeckRender, type DeclarativeDocument } from "@runstamp/react";
const document: DeclarativeDocument = {
title: "Headless example",
slides: [{ layout: "title", title: "Headless Runstamp" }],
};
function CurrentSlide() {
const { title, slideIndex, slideCount } = useDeckRender();
return <h1>{title} — {slideIndex + 1} / {slideCount}</h1>;
}
export default function HeadlessDeck() {
return <DeckProvider deck={document}><CurrentSlide /></DeckProvider>;
}
Calling either hook without a source and without a parent provider throws an actionable error.
Node server renderer
Never import the server entry point into a Client Component. It wraps the native engine for server routes and scripts.
import { writeFile } from "node:fs/promises";
import type { DeclarativeDocument } from "@runstamp/pptx";
import { createRunstampRenderer } from "@runstamp/react/server";
const renderer = createRunstampRenderer();
const document: DeclarativeDocument = {
title: "Server-rendered deck",
slides: [{ layout: "title", title: "Generated on the server" }],
};
const buffer = await renderer.renderPptx(document);
await writeFile("server-rendered.pptx", buffer);
The adapter also exposes renderPdf(document, options?). Convenience exports renderDeckToPptx(document, options?) and renderDeckToPdf(document, options?) create the default adapter for one render.
In a Next.js Route Handler, export runtime = "nodejs", return the bytes directly, and set the PPTX content type documented in Troubleshooting.