v3.0.0

<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

PropertyAttributeTypeDefaultDescription
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

EventDetailDescription
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

MethodSignatureDescription
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

PartDescription
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

PropertyDescription
--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.