PowerPoint
Build a PPTX viewer or editor with Svelte 5. Open a file, add slide navigation, connect edits, and download the result.
Open a file
Read your PPTX file into a Uint8Array. The examples below accept these bytes as a component prop.
const bytes = new Uint8Array(await file.arrayBuffer());Render a slide
Open the bytes with openPresentationArtifact and pass its document and a slide to PresentationSlideView. Give the viewer a fixed height so it can size the slide.
<script lang="ts">
import { untrack } from 'svelte';
import { openPresentationArtifact } from '@tumblerjs/slides';
import { PresentationSlideView } from '@tumblerjs/svelte/slides';
let { bytes }: { bytes: Uint8Array } = $props();
const artifact = untrack(() => openPresentationArtifact(bytes));
let scale = $state(1);
</script>
<div style="height: 540px; min-width: 0">
{#if artifact.document.slides[0]}
<PresentationSlideView
presentation={artifact.document}
slide={artifact.document.slides[0]}
bind:scale
/>
{/if}
</div>A scale of 1 fits the slide to the viewer. Bind scale to keep zoom controls in sync with pinch gestures. These examples open one file per component instance; remount the component when loading another file.
Add slide navigation
Bind PresentationSlideRail to an index and use that index for the main view. Handle onslide so links inside the presentation can switch slides too.
<script lang="ts">
import type { PresentationDocument } from '@tumblerjs/slides';
import { PresentationSlideRail, PresentationSlideView } from '@tumblerjs/svelte/slides';
let { presentation }: { presentation: PresentationDocument } = $props();
let index = $state(0);
</script>
<div style="display: flex; height: 540px; min-width: 0">
<aside style="width: 150px; flex-shrink: 0">
<PresentationSlideRail {presentation} bind:index />
</aside>
<div style="flex: 1; min-width: 0">
{#if presentation.slides[index]}
<PresentationSlideView
{presentation}
slide={presentation.slides[index]!}
onslide={(part) => {
const next = presentation.slides.findIndex(slide => slide.part === part);
if (next >= 0) index = next;
}}
/>
{/if}
</div>
</div>Connect editing
Use an editing session instead of a read-only artifact. Set editable on the view and connect its callbacks to the session. Reassign the returned artifact after each edit so the view updates.
<script lang="ts">
import { untrack } from 'svelte';
import { openPresentationEditingSession, type PresentationArtifact } from '@tumblerjs/slides';
import { PresentationSlideView } from '@tumblerjs/svelte/slides';
let { bytes }: { bytes: Uint8Array } = $props();
const session = untrack(() => openPresentationEditingSession(bytes));
let artifact = $state(session.artifact);
let index = $state(0);
let canUndo = $state(false);
let canRedo = $state(false);
let error = $state('');
function apply(operation: () => PresentationArtifact) {
try {
artifact = operation();
canUndo = session.canUndo;
canRedo = session.canRedo;
error = '';
} catch (cause) {
error = cause instanceof Error ? cause.message : 'Could not apply this edit.';
}
}
</script>
<button disabled={!canUndo} onclick={() => apply(() => session.undo())}>Undo</button>
<button disabled={!canRedo} onclick={() => apply(() => session.redo())}>Redo</button>
{#if error}<p role="alert">{error}</p>{/if}
<div style="height: 540px; min-width: 0">
{#if artifact.document.slides[index]}
<PresentationSlideView
presentation={artifact.document}
slide={artifact.document.slides[index]!}
editable
onobjectchange={(change) => apply(() => session.updateObject(change))}
ontextedit={(change) => apply(() => session.editText(change))}
onformat={(change) => apply(() => session.formatText(change))}
onshapechange={(change) => apply(() => session.styleShape(change))}
onundo={(redo) => apply(() => redo ? session.redo() : session.undo())}
/>
{/if}
</div>The text and shape formatting controls appear when their callbacks are connected. To add the thumbnail rail, pass artifact.document as its presentation and bind the same index used by the view.
Use the editor
| Action | Control |
|---|---|
| Move an object | Drag it, or select it and use the arrow keys. |
| Resize or rotate | Drag an edge or corner handle to resize. Use the handle above the object to rotate; hold Shift to snap to 15° increments. |
| Edit text | Double-click a text box, or select it and press Enter. Select words to apply formatting with the toolbar. |
| Edit a table | Double-click a cell. Tab and Shift+Tab move between cells. |
| Finish editing | Press Escape or Ctrl+Enter. Click blank slide space to clear the selection. |
| Change shape colours | Select the shape, then use the fill and outline controls. |
Download the edited file
Add this function to the editor script and call it from your Download button. Call bytes() when saving, not after every edit: it builds the PPTX archive on demand.
function download() {
const bytes = session.artifact.bytes();
const blob = new Blob([Uint8Array.from(bytes).buffer], {
type: 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
});
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'presentation.pptx';
link.click();
setTimeout(() => URL.revokeObjectURL(url), 1000);
}<button onclick={download}>Download</button>For server storage, send the returned Uint8Array through your upload flow instead. Edits remain in memory until you save them.