Customization

Menus

Customize the PPTX editor menu bar and context menus with registered slot rows, application rows, groups, and submenus.

Compose PptxEditor.Menu and PptxEditor.ContextMenu independently. Rows for registered slots keep their label, icon, shortcut, enabled state, and action. For every part and row, see the menu reference.

Customize the menu bar

Use Menu.Item with a slotId for an editor command, and Menu.Row for your own action. A named menu child changes its menu in place. Menu.Entry adds your own menu.

<PptxEditor.Menu>
  <PptxEditor.Menu.File preset={false}>
    <PptxEditor.Menu.Row label="Open…" icon="folder_open" onSelect={openFile} />
    <PptxEditor.Menu.Item slotId="file.pageSetup" />
  </PptxEditor.Menu.File>
  <PptxEditor.Menu.Edit>
    <PptxEditor.Menu.Item slotId="more.delete" hidden />
  </PptxEditor.Menu.Edit>
  <PptxEditor.Menu.Arrange hidden />
  <PptxEditor.Menu.Entry id="help" label="Help">
    <PptxEditor.Menu.Row label="Documentation" icon="info" href="https://example.com/docs" />
  </PptxEditor.Menu.Entry>
</PptxEditor.Menu>

Render the menu bar inside PptxEditor.Viewport. preset={false} on one menu renders only your rows in it. Use Group, Separator, and Submenu to arrange rows. A row's href opens in a new tab, and only links that pass sanitizeHref work. For the rules of the built-in menus, see The built-in menu bar.

Use a named row

A named row is shorthand for one registry row. <PptxEditor.Menu.Cut hidden /> is <PptxEditor.Menu.Item slotId="edit.cut" hidden />. Put a row in the menu that holds it.

<PptxEditor.Menu>
  <PptxEditor.Menu.Edit>
    <PptxEditor.Menu.Cut hidden />
    <PptxEditor.Menu.Copy label="Copy to clipboard" icon="content_copy" />
  </PptxEditor.Menu.Edit>
</PptxEditor.Menu>

A menu icon is an element or the name of a packaged Material Symbol, such as info. icon={null} removes the icon. For every name, see Named rows.

Customize the context menu

Place PptxEditor.ContextMenu inside PptxEditor.Root or PptxEditor.Viewport. Render a row's slot with hidden to remove the row in place. Its command keeps working from the toolbar, keyboard shortcuts, and the API.

<PptxEditor.Root document={bytes}>
  <PptxEditor.Viewport>
    <PptxEditor.Content />
    <PptxEditor.ContextMenu disabled={readOnly} onOpenChange={setMenuOpen}>
      <PptxEditor.ContextMenu.Slot slotId="text.link" hidden />
      <PptxEditor.ContextMenu.Item label="Ask legal" icon="info" onSelect={askLegal} />
    </PptxEditor.ContextMenu>
  </PptxEditor.Viewport>
</PptxEditor.Root>

The text.link slot is the Insert link… row of the text context menu. A placed menu gives the canvas the same entry as popups.contextMenu. An explicit popups.contextMenu entry, a renderer or false, wins over a placed menu. A context menu outside the root renders nothing and prints a development warning.

  • disabled shows no canvas menu, and an open menu closes. The canvas still takes the right-click, so the browser's own menu does not show either. This is the same as popups.contextMenu: false.
  • onOpenChange(open) reports when the menu opens and closes. In Vue, listen to openChange.

Both props also work on a menu that you render from popups.contextMenu.

Add an application row

ContextMenu.Item supplies all of its behavior. Pass a label and an onSelect handler. To disable the row, pass disabled and a disabledReason.

To show a row for some targets only, read useContextMenuRequest() in a component that you register with definePopup(). Pass false instead of the row when it does not apply. A child component that renders nothing still counts as a row, and leaves a separator at the end of the menu. Do not call a hook inside an inline renderer.

import {
  definePopup,
  PptxEditor,
  useContextMenuRequest,
  type PptxEditorContextMenuProps,
  type PptxEditorPopups,
} from '@pptx-editor.dev/react';

function AcmeContextMenu(props: PptxEditorContextMenuProps) {
  const request = useContextMenuRequest();
  return (
    <PptxEditor.ContextMenu {...props}>
      {(request?.kind === 'shapes' || request?.kind === 'picture') && (
        <PptxEditor.ContextMenu.Item label="Ask legal" icon="info" onSelect={askLegal} />
      )}
    </PptxEditor.ContextMenu>
  );
}

const popups: PptxEditorPopups = { contextMenu: definePopup(AcmeContextMenu) };

request.kind is shapes, picture, chart, diagram, text, table, tableFrame, or slide. In Vue, PptxEditorContextMenu has the same parts, and a row's onSelect is the select event.

Change the section menu

A popups.sectionContextMenu renderer receives SectionContextMenuProps: the section, the menu state (at, keyboard, and owner), onClose, onRename, and onRemoved. This example keeps the packaged menu, so it shows the shape of the entry. Replace the body with your own rows.

import {
  definePopup,
  PptxEditor,
  SectionContextMenu,
  type PptxEditorPopups,
  type SectionContextMenuProps,
} from '@pptx-editor.dev/react';

function AcmeSectionMenu(props: SectionContextMenuProps) {
  return <SectionContextMenu {...props} />;
}

const popups: PptxEditorPopups = { sectionContextMenu: definePopup(AcmeSectionMenu) };

export function DeckEditor({ bytes }: { bytes: Uint8Array }) {
  return (
    <PptxEditor.Root document={bytes} popups={popups}>
      <PptxEditor.Viewport>
        <PptxEditor.SlideRail />
        <PptxEditor.Content />
      </PptxEditor.Viewport>
    </PptxEditor.Root>
  );
}

Rename in your own menu calls onRename, which opens the name field in the rail or the sorter that opened the menu. Renaming a section is not one engine command until the name is typed: the name field runs renameSection. Call onClose when a row runs, and onRemoved after a row removes sections, so focus goes to the current slide. Set the entry to false to show no section menu. The section commands keep working from the API. The slide menu of the rail and the sorter is popups.railContextMenu, and works the same way with RailContextMenu.

Limits

  • Hiding a row changes the menu only. The operation keeps working from the toolbar, keyboard shortcuts, and the API.
  • A row that opens a dialog stays in the menu, disabled with a reason, when its popups entry is false. See Rows for dialogs and popups.
  • The built-in menu bar places no slide.layout, slide.reset, or section.* row. See Menu-only slots.

For application-owned menus, account for these limits:

  • Add file actions with Menu.Row inside Menu.File. There are no onOpen, onSave, fileName, or exporters props.
  • The menu bar does not move overflow into a More menu. Reserve enough width for your menu labels.
  • A popups.contextMenu renderer receives no { t } argument. Pass t to PptxEditor.ContextMenu in the renderer, or call useChromeTranslate() in a component that you register with definePopup().

See also