<bmx-presentation-editor>
A slide editor in the page that opens and saves PowerPoint .pptx files, with nothing sent anywhere.
11 properties · 9 events · 19 methods · 9 parts
Example
Try it with your own files in confidence: they are opened by your browser, on your device. Nothing is uploaded, no copy reaches us or anyone else, and nothing is kept once you close this tab.
A fictional studio's design review, made from an outline in the editor's own theme: a title slide, lists at several levels, a process drawn with shapes, a table, a chart drawn from its data and a picture, with speaker notes, transitions and a build. Edit anything (double-click the chart to change its numbers), try the Design and Animate tabs, present it, then Save .pptx and open it in PowerPoint - or open a .pptx of your own.
Show markup
<div class="row" style="gap: 0.75rem; flex-wrap: wrap; align-items: center; margin-block-end: 0.75rem">
<button type="button" id="ex-pe-sample">Load the sample deck</button>
<button type="button" id="ex-pe-new">New presentation</button>
<button type="button" id="ex-pe-show">Present</button>
<button type="button" id="ex-pe-presenter">Presenter view</button>
<label><input type="checkbox" id="ex-pe-read" /> Read only</label>
</div>
<bmx-presentation-editor id="ex-pe" locale="en-GB" label="Sample presentation" style="--bmx-presentation-editor-height: 46rem"></bmx-presentation-editor>
<p class="note" id="ex-pe-out" role="status">A fictional studio's design review, made from an outline in the editor's own theme: a title slide, lists at several levels, a process drawn with shapes, a table, a chart drawn from its data and a picture, with speaker notes, transitions and a build. Edit anything (double-click the chart to change its numbers), try the Design and Animate tabs, present it, then Save .pptx and open it in PowerPoint - or open a .pptx of your own.</p>
<script type="module">
await customElements.whenDefined('bmx-presentation-editor');
const el = document.getElementById('ex-pe');
const out = document.getElementById('ex-pe-out');
// A mood-board picture, drawn here so the sample needs no files.
const picture = () =>
new Promise(resolve => {
const c = document.createElement('canvas');
c.width = 960;
c.height = 600;
const g = c.getContext('2d');
g.fillStyle = '#f5efe6';
g.fillRect(0, 0, 960, 600);
g.fillStyle = '#c2410c';
g.fillRect(90, 230, 380, 270);
g.fillStyle = '#334155';
g.fillRect(540, 150, 230, 350);
g.fillStyle = '#0f766e';
g.beginPath();
g.arc(300, 160, 82, 0, Math.PI * 2);
g.fill();
g.fillStyle = '#d6b88f';
g.fillRect(0, 520, 960, 80);
c.toBlob(resolve, 'image/png');
});
const loadSample = async () => {
await el.newPresentation();
const step = (shape, text, x) => ({ shape, text, x, y: 220, w: 190, h: 90 });
await el.fromOutline([
{ title: 'Harbour Street café', subtitle: 'Design review · Lumen & Loom · October 2026', notes: 'Welcome everyone. Twenty minutes, then questions.' },
{ title: 'Agenda', bullets: ['Where we are', 'How we work', 'Budget so far', 'Mood board', 'Next steps'] },
{
title: 'Where we are',
bullets: ['Concepts signed off', { text: 'Warm timber, terracotta and a sea green', level: 1 }, { text: 'Pendants lowered over the counter', level: 1 }, 'Boards for the client on Monday', { text: 'Printed and as a PDF', level: 1 }],
notes: 'Mention the client asked for the boards a week early.',
},
{ title: 'How we work', shapes: [step('homePlate', 'Brief', 70), step('chevron', 'Concepts', 280), step('chevron', 'Boards', 490), step('chevron', 'Build', 700)] },
{
title: 'Budget so far',
table: [
['Item', 'Finish', 'Supplier', 'Cost'],
['Counter top', 'Oiled oak', 'Northwood', '1,240'],
['Wall tiles', 'Terracotta', 'Clay & Co', '860'],
['Pendants', 'Brushed brass', 'Lumen Works', '420'],
],
},
{
title: 'Hours by stage',
chart: {
type: 'column',
grouping: 'stacked',
categories: ['Brief', 'Concepts', 'Boards', 'Build'],
series: [
{ name: 'Design', values: [6, 28, 18, 10] },
{ name: 'Site', values: [2, 4, 6, 26] },
],
legend: 'b',
labels: false,
},
notes: 'Build is mostly site time, as planned.',
},
{ title: 'Mood board' },
{ title: 'Next steps', bullets: ['Boards to the client: Monday', 'Lighting quote confirmed: Wednesday', 'Site visit: Friday'], notes: 'Ask who can come to the site visit.' },
]);
// A fade between slides, and the process steps coming in one after another.
const deck = await el.getDeck();
await el.setDeck({
...deck,
slides: deck.slides.map((s, i) => ({
...s,
transition: { type: i === 0 ? 'fade' : 'push', dir: 'l', duration: 600 },
...(i === 3 ? { builds: s.shapes.slice(1).map((sh, k) => ({ id: sh.id, effect: 'fly', dir: 'l', trigger: k === 0 ? 'click' : 'after', duration: 400, delay: 0 })) } : {}),
})),
});
await el.goTo(6);
await el.insertPicture(await picture(), 'Terracotta, slate and sea green swatches on a sand background');
await el.select([]);
await el.goTo(0);
el.fileName = 'Harbour Street review.pptx';
};
document.getElementById('ex-pe-sample').addEventListener('click', loadSample);
document.getElementById('ex-pe-new').addEventListener('click', () => el.newPresentation());
document.getElementById('ex-pe-show').addEventListener('click', () => el.present(0));
document.getElementById('ex-pe-presenter').addEventListener('click', () => el.present(0, true));
document.getElementById('ex-pe-read').addEventListener('change', e => (el.readOnly = e.target.checked));
el.addEventListener('bmxOpen', e => (out.textContent = `Opened ${e.detail.name}: ${e.detail.slides} slide${e.detail.slides === 1 ? '' : 's'}.`));
el.addEventListener('bmxSlideChange', e => (out.textContent = `Slide ${e.detail.index + 1}${e.detail.title ? `: ${e.detail.title}` : ''}.`));
el.addEventListener('bmxShow', e => (out.textContent = e.detail.ended ? `Slide show ended on slide ${e.detail.index + 1}.` : `Slide show from slide ${e.detail.index + 1}${e.detail.presenter ? ', presenter view' : ''}.`));
el.addEventListener('bmxViewChange', e => (out.textContent = `${{ normal: 'Normal', sorter: 'Slide sorter', master: 'Master' }[e.detail.view]} view.`));
el.addEventListener('bmxExport', e => (out.textContent = `Exported ${e.detail.name} (${Math.round(e.detail.data.length / 1024)} KB).`));
el.addEventListener('bmxSave', e => (out.textContent = `Saved ${e.detail.name} (${Math.round(e.detail.data.length / 1024)} KB).`));
await loadSample();
</script>
WHAT IT DOES
- Opens a .pptx and shows it as PowerPoint lays it out: the master's and layout's artwork and placeholders, theme colours and fonts, text with its list levels, bullets and numbering, shapes, pictures, tables, groups, connectors and charts (drawn from their data). Diagrams and embedded objects are shown and kept, and travel back as they came.
- Charts: column, bar, line, area, pie, doughnut and scatter, inserted or edited from a data grid; the data goes into the file with its own workbook, so PowerPoint's Edit Data still works.
- Slide show: full screen, with transitions and objects that come in a click at a time; a presenter view with the next slide, the notes, a timer and a second window for the audience's screen.
- Animate: a transition per slide (or all), entrances per object (when they start, from where, how long, in what order).
- Design: theme presets, theme colours and fonts, slide size (content scaled to fit), and master view for editing the masters and layouts with the same tools as a slide. A slide sorter to see and reorder all the slides.
- Exports: PDF, a slide or every slide as PNG, and printing.
- Slides: new from any of the deck's layouts, duplicate, delete, hide, reorder by dragging or with the keyboard, change layout.
- Shapes: text boxes, forty-odd preset shapes, lines and arrows, pictures, tables; move, resize, rotate, flip, with smart guides; in front and behind, group and ungroup, align and distribute; connectors stay on the shapes they join.
- Text: typed in place, in the deck's own fonts and sizes; bold, italic, underline, strikethrough, colour, size, font, alignment, bullets, numbering and list levels; shrink-to-fit and grow-to-fit as the file asks.
- Fill and outline from the theme's colours (which follow the theme) or any colour; outline weight, dashes and arrowheads.
- Speaker notes, undo and redo, copy and paste (a picture or text pasted from elsewhere arrives as one), alternative text for every object, and a list of the slide's objects to select and hide them.
- Saves a .pptx PowerPoint opens: slides that were not touched are written back as they were read.
Properties
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
allowOpen |
allow-open |
boolean |
true |
Show Open and accept dropped files. |
compactBelow |
compact-below |
number |
760 |
Below this width (pixels) the slide list moves under the slide and the pane opens over it. |
fileName |
file-name |
string |
'Presentation.pptx' |
The file name Save uses. |
label |
label |
string |
— | The element's accessible name. |
locale |
locale |
string |
— | The locale for numbers and dates. Unset: the page's lang. |
notes |
notes |
boolean |
true |
Show the speaker notes under the slide. |
readOnly |
read-only |
boolean |
false |
Show the deck without editing tools. |
src |
src |
string |
— | A .pptx to open by address (fetched from the page's own server). |
strings |
strings |
Partial<BmxPresentationEditorStrings> | string |
— | Replacements for any of the wording (an object, or its JSON). |
units |
units |
BmxPresentationEditorUnit |
— | Units for the size and position fields. Unset: inches for US English, centimetres otherwise. |
view |
view |
BmxPresentationEditorView |
'normal' |
What is shown: normal (slides to edit), sorter (all slides as a grid) or master (the masters and layouts). |
Events
| Event | Detail | Description |
|---|---|---|
bmxChange |
BmxPresentationEditorChangeDetail |
Fired after every change to the deck. |
bmxError |
{ message: string; name?: string | undefined; } |
Fired when a file cannot be opened. |
bmxExport |
BmxPresentationEditorExportDetail |
Fired before an export downloads. Cancelable: cancel to keep the file yourself. |
bmxOpen |
BmxPresentationEditorOpenDetail |
Fired when a file is opened or a new presentation started. |
bmxSave |
BmxPresentationEditorSaveDetail |
Fired before Save downloads the file. Cancelable: cancel to store it yourself. |
bmxSelectionChange |
BmxPresentationEditorSelectionDetail |
Fired when the selected objects change. |
bmxShow |
BmxPresentationEditorShowDetail |
Fired when the slide show starts and when it ends. |
bmxSlideChange |
BmxPresentationEditorSlideDetail |
Fired when another slide is shown. |
bmxViewChange |
BmxPresentationEditorViewDetail |
Fired when the view changes (normal, sorter, master). |
Methods
| Method | Signature | Description |
|---|---|---|
addSlide |
addSlide(layoutName?: string) => Promise<number> |
Add a slide after the current one, from a layout by name (the deck's second layout if none is named). |
download |
download(name?: string) => Promise<void> |
Save the deck as a .pptx download (unless bmxSave is cancelled). |
endShow |
endShow() => Promise<void> |
End the slide show. |
exportPdf |
exportPdf(scale?: number) => Promise<Uint8Array> |
The slides as a PDF, a page per slide (hidden slides left out), scale pixels to the point. |
exportPng |
exportPng(index?: number, scale?: number) => Promise<Blob> |
A slide as a PNG (0-based; the current one if none is given), scale pixels to the point. |
fromOutline |
fromOutline(slides: BmxPresentationOutlineSlide[]) => Promise<void> |
Replace the slides with ones made from an outline, in this deck's own design (its masters, layouts and theme). Undoable. |
getDeck |
getDeck() => Promise<BmxPresentationDeck> |
The deck model (treat as read-only; change it with setDeck). |
goTo |
goTo(index: number) => Promise<void> |
Show a slide (0-based). |
insertPicture |
insertPicture(file: Blob, alt?: string) => Promise<void> |
Put a picture on the current slide. |
newPresentation |
newPresentation(width?: number, height?: number) => Promise<void> |
Start a new presentation (16:9 unless a size in points is given). |
open |
open(data: Uint8Array | ArrayBuffer | Blob, name?: string) => Promise<void> |
Open a .pptx (bytes, a Blob or a File). |
present |
present(from?: number, presenter?: boolean) => Promise<void> |
Start the slide show (from a slide, 0-based; the current one if none is given), full screen, optionally with the presenter view. |
print |
print() => Promise<void> |
Print the slides (or save them as PDF with the browser's print), a page per slide, the text as text. |
redo |
redo() => Promise<void> |
|
save |
save() => Promise<Uint8Array> |
The deck as a .pptx file. |
select |
select(ids?: number[]) => Promise<void> |
Select objects on the current slide by id (none clears). |
setDeck |
setDeck(deck: BmxPresentationDeck) => Promise<void> |
Replace the deck (undoable). |
slideSvg |
slideSvg(index: number) => Promise<string> |
A slide drawn as SVG markup (0-based), for thumbnails or printing elsewhere on the page. |
undo |
undo() => Promise<void> |
CSS shadow parts
| Part | Description |
|---|---|
canvas |
The slide being edited. |
dialog |
The chart data dialog. |
menu |
A drop-down menu. |
notes |
The speaker notes. |
pane |
The format and objects pane. |
show |
The slide show and presenter view. |
slides |
The slide thumbnails. |
sorter |
The slide sorter. |
toolbar |
The tabs and tools. |
CSS custom properties
| Property | Description |
|---|---|
--bmx-presentation-editor-accent |
Selection, handles and the current slide. |
--bmx-presentation-editor-guide |
Smart guides while dragging. |
--bmx-presentation-editor-height |
Height of the editor. |
--bmx-presentation-editor-pane-width |
Width of the format pane. |
--bmx-presentation-editor-radius |
Corner radius of the frame. |
--bmx-presentation-editor-slides-width |
Width of the slide list. |
--bmx-presentation-editor-stage |
Behind the slide. |