Components
API reference for all Presentation primitives.
Root
The outermost component. Creates an internal store (or inherits one from Presentation.Provider), loads the file, and provides context to all descendants.
import * as Presentation from "@diceui/pptx";
<Presentation.Root
file={file}
readOnly={false}
defaultZoom={0.5}
onLoad={(store) => {}}
onError={(error) => {}}
onSlideChange={({ slideId, index, reason }) => {}}
onStatusChange={({ status }) => {}}
onEdit={({ operation, source }) => {}}
onHistoryChange={({ canUndo, canRedo, isDirty }) => {}}
>
{/* children */}
</Presentation.Root>;onSlideChange triggers for every change of the active slide. Use reason to tell user navigation ("navigate") apart from a completed load ("load"), an edit that moved the active slide ("edit"), and a cleared viewer ("reset").
onEdit triggers after an edit is applied, undone, or redone, with source naming which of the three it was. onHistoryChange triggers whenever undo/redo availability or the unsaved-changes flag moves, which is enough to drive a toolbar without polling the store.
Prop
Type
Provider
Makes an existing store available to descendants without rendering any DOM. Use it when a toolbar or debug bar has to sit outside Presentation.Root's layout, or when the store is created with useCreatePresentationStore before the tree mounts. See Store for the controlled setup.
const store = useCreatePresentationStore();
<Presentation.Provider store={store}>
<Toolbar />
<Presentation.Root>{/* … */}</Presentation.Root>
</Presentation.Provider>;Presentation.Root inherits the store from Provider when both wrap the same tree, so there is still only one source of truth.
Prop
Type
Viewport
Scrollable container that centers the slide and optionally auto-fits it to fill the available space.
<Presentation.Viewport
autoFit
autoFitPadding={10}
scrollNavigation
scrollZoom
onZoomChange={({ zoom, reason }) => setZoom(`${Math.round(zoom * 100)}%`)}
>
<Presentation.Slide />
</Presentation.Viewport>onZoomChange covers every zoom change, including the automatic fits performed while autoFit is on. Its reason tells them apart: "fit" is auto-fit reacting to a resize, "zoom" is a level someone asked for.
scrollNavigation makes the wheel behave like PowerPoint: scrolling past the end of the slide advances, past the start goes back. It is off by default because it captures wheel events, which fights a page that already scrolls.
scrollZoom points the browser's zoom gesture at the deck instead of the page: Ctrl/Cmd + wheel and trackpad pinch change the zoom level, keeping whatever is under the pointer in place, as PowerPoint and every canvas editor do. It is off by default for the same reason as scrollNavigation — a viewer embedded in a page should not take page zoom away from a reader who is using it to read. Turn it on when the presentation owns the window. Ctrl/Cmd and the plus and minus keys still zoom the page either way.
Because the gesture sets an explicit level, it releases autoFit, so a zoom control built like the one below flips from Fit · 82% to a plain percentage as soon as someone zooms.
autoFit is the starting mode, not a latch. Setting an explicit level turns fitting off so the next resize leaves that level alone, and setAutoFit(true) turns it back on, which means a zoom control offering both keeps no state of its own:
function ZoomSelect() {
const { zoom, isAutoFit, setZoom, setAutoFit } = useZoom();
return (
<select
value={isAutoFit ? "fit" : String(zoom)}
onChange={(event) => {
const { value } = event.target;
if (value === "fit") setAutoFit(true);
else setZoom(Number(value));
}}
>
<option value="fit">Fit · {Math.round(zoom * 100)}%</option>
<option value="1">100%</option>
<option value="2">200%</option>
</select>
);
}The viewport keeps measuring itself while fitting is off, so re-arming picks up any resize that happened in between. To open a deck at a fixed level, pass defaultZoom to Root, which only applies when nothing is auto-fitting over it.
Prop
Type
Slide
Renders the active slide as a scaled DOM tree. Mounts an empty wrapper immediately so sibling layout is stable. Children are placed in an absolute inset-0 overlay.
<Presentation.Slide onNodeError={(nodeId, error) => reportError(error, { nodeId })}>
<Presentation.Selection />
</Presentation.Slide>A shape that fails to render is skipped rather than breaking the slide. onNodeError surfaces those failures; without a handler they are logged with console.warn.
The wrapper carries a data-status attribute matching the current load status:
[data-status="loading"] {
opacity: 0.5;
}Prop
Type
Selection
PowerPoint-style editing overlay. Enables drag-to-move, resize, inline text editing, multi-select, and keyboard shortcuts. Renders nothing unless the presentation was loaded with readOnly={false}.
Must be placed inside Presentation.Slide.
<Presentation.Slide>
<Presentation.Selection
onUndo={(status) => status === "empty" && toast.error("Nothing to undo")}
onRedo={(status, error) => {
if (error) toast.error(error instanceof Error ? error.message : "Redo failed");
else if (status === "empty") toast.error("Nothing to redo");
}}
onNodeDelete={(id, error) => error && toast.error("Could not delete")}
onNodeTransform={(id, error) => error && toast.error("Could not move")}
onTextChange={(id, error) => error && toast.error("Text edit failed")}
onSelectionChange={({ nodes }) => setInspectedShape(nodes[0] ?? null)}
onModeChange={(mode) => setToolbarDisabled(mode === "move" || mode === "resize")}
/>
</Presentation.Slide>onSelectionChange is how UI outside the overlay (a formatting toolbar, a properties panel) follows the selection. It also triggers with an empty selection when the active slide changes, since navigating away drops the selection.
Interaction model
| Action | Behavior |
|---|---|
| Click text box / placeholder | Select + enter text edit mode |
| Click regular shape | Select |
| Double-click regular shape | Enter text edit mode |
| Type while shape selected | Enter text edit mode |
| Drag shape | Move |
| Drag border of text box (in text mode) | Move while keeping text mode |
| Drag resize handle | Resize |
| Shift + drag corner handle | Resize preserving aspect ratio |
| Ctrl/Cmd + A | Select all |
| Shift / Ctrl / Cmd + click | Toggle shape in/out of selection |
| Drag empty canvas | Marquee select |
| Delete / Backspace | Delete selected |
| Arrow keys | Nudge (1 px; Shift = 10 px) |
| Ctrl/Cmd + Z | Undo |
| Ctrl/Cmd + Y or Ctrl/Cmd + Shift + Z | Redo |
| Escape | Exit text mode → deselect |
Undo and redo shortcuts are opt-in via undoRedoShortcuts. Everything else in the table is always bound.
Theming
Control the accent color with a CSS variable:
--presentation-selection: #2563eb; /* default */<Presentation.Selection className="[--presentation-selection:var(--ring)]" />Prop
Type
ThumbnailList
Scrollable list of slide thumbnails. Each ThumbnailItem is mounted immediately, while its preview is rendered lazily as it approaches the viewport and cached. Use ↑/↓, Home, and End to navigate with roving focus. Set loop to wrap arrow-key navigation.
<Presentation.ThumbnailList loop />Custom layout
Pass a render function to children for full control over each item:
<Presentation.ThumbnailList>
{({ slides }) =>
slides.map((slide) => (
<Presentation.ThumbnailItem key={slide.id} slideId={slide.id}>
<Presentation.ThumbnailItemPreview />
<Presentation.ThumbnailItemNumber />
</Presentation.ThumbnailItem>
))
}
</Presentation.ThumbnailList>Intercepting navigation
onSelect runs just before an item becomes the active slide. Call preventDefault() to stop it:
<Presentation.ThumbnailItem
slideId={slide.id}
onSelect={(event) => {
if (store.isDirty() && !confirmDiscard()) event.preventDefault();
}}
/>The list navigates on focus, so this triggers for keyboard roving as well as clicks. That is also why the veto lives here rather than in onClick: the browser delivers focus before click, so a handler that ran on click would arrive after the slide had already changed. Use onClick for side effects that do not need to block navigation.
onSelect is also what lets a sortable library suppress navigation mid-drag. The strip ships no drag-and-drop of its own; see Reordering slides for a worked example.
Prop
Type
Prop
Type
Prop
Type
Prop
Type
Loading
Renders its children only while the presentation is in the "loading" state.
<Presentation.Loading>{(progress) => <span>Loading {progress}%</span>}</Presentation.Loading>Prop
Type
Error
Renders its children only when the presentation fails to parse.
<Presentation.Error>{(err) => <span>{err.message}</span>}</Presentation.Error>Prop
Type