Customization

Toolbar

Customize the PPTX editor toolbar with named controls, command slots, application actions, groups, and responsive overflow.

Customize the toolbar through named parts, slots, and groups. Each control keeps its command, enabled state, and refusal reason. For every identifier and rule, see the toolbar reference.

Tooltips appear immediately during keyboard navigation. Pointer tooltips wait briefly on the first control. Moving to nearby controls skips the delay and entry animation.

Customize a toolbar control

Pass className, icon, hidden, or asChild to a named part.

<PptxEditor.Toolbar>
  <PptxEditor.Toolbar.Bold icon={<BoldIcon />} className="acme-bold" />
  <PptxEditor.Toolbar.Strike hidden />
</PptxEditor.Toolbar>

hidden on a named part is shorthand for <Toolbar.Slot slotId="text.strike" hidden />. A control shows its state as data-active and data-disabled attributes for your CSS. A slot passes only className to its control. Any other attribute on it, such as style, id, or data-*, prints a development warning. For the part names, see Named parts.

Replace one control

Toolbar.Slot replaces one built-in control with your own content. Your content keeps the control's place and collapses with its group. No control renders, so className and icon on the slot (class in Vue) print a development warning. Put them on your content.

<PptxEditor.Toolbar>
  <PptxEditor.Toolbar.Slot slotId="font.family">
    <MyFontPicker />
  </PptxEditor.Toolbar.Slot>
</PptxEditor.Toolbar>

To keep the control's behavior on your own element, use asChild (as-child in Vue). Forward its attributes and events:

<PptxEditor.Toolbar.Bold asChild>
  <button type="button" className="acme-button">
    B
  </button>
</PptxEditor.Toolbar.Bold>

Choose the toolbar arrangement

ConfigurationHow to use it
One controlPass className, icon, or hidden to its named part.
A different elementUse asChild on a button control. Forward its attributes and events.
One registered slotUse Toolbar.Slot with slotId.
A built-in groupUse Toolbar.Group with its registered id. Add children or set hidden.
An application groupChoose another id, then set its label, position, and overflow behavior.
An application buttonUse Toolbar.Action.
A complete arrangementSet preset={false} and compose all visible controls.
Narrow widthsKeep overflow on, or set overflow={false} to let the bar wrap. A preset={false} bar always wraps.
Every group at all timesSet contextual={false}. By default, the bar shows only the groups for the selection.
Your own labelsPass the t prop. Your parts read it with useChromeTranslate() or useToolbarLabel(). See Labels.

For the groups that each selection shows, see Registered groups and slots.

Group your own controls

Put a Toolbar.Action in your own Toolbar.Group. The action keeps the slide selection. When the toolbar runs out of width, a whole group moves into the More menu. The group's label becomes its heading there.

<PptxEditor.Toolbar>
  <PptxEditor.Toolbar.Group id="review" label="Review" priority={15} after="text">
    <PptxEditor.Toolbar.Action label="Send for review" onSelect={send} />
  </PptxEditor.Toolbar.Group>
  <PptxEditor.Toolbar.Group id="find" hidden />
</PptxEditor.Toolbar>

Give your group a label, or a labelKey from your translation catalog. The id of a built-in group, such as find, changes that group instead. A lower priority leaves the bar first. hidden on a group removes the whole group. For every prop, see Group options and Action options.

Use the editor's icons

Icon draws the editor's Material Symbols as an inline SVG, so your controls match the toolbar. Both adapters export it.

<PptxEditor.Toolbar.Action label="Present" icon={<Icon name="slideshow" />} onSelect={present} />
PropUse
nameA Material Symbol name, such as slideshow or close. An unknown name prints a development warning.
pathsYour own SVG path data in place of name, for a 0 -960 960 960 view box.
classNameThe class of the svg element. The default, pptx-icon, gives the toolbar size, 18px, inside the editor. Give "" or your own class to size the icon yourself.

Compose the font picker

Toolbar.FontFamily is a compound. It holds the picker state in a part-level context. With no children, it renders the default trigger and the grouped list with a search field. With children, compose its parts:

  • Toolbar.FontFamily.Trigger is the button that opens the list. Its children replace the current family name.
  • Toolbar.FontFamily.Content is the list while it is open. Its children replace the search field and the default groups.
  • Toolbar.FontFamily.Item is one option. Its value is a family name, or a theme font reference: +mj-lt for the headings font, +mn-lt for the body font. A theme item shows the theme's family and its role, and the text keeps the reference. The item carries the check mark while the text uses that font. Its children replace the name.
const { FontFamily } = PptxEditor.Toolbar;

<PptxEditor.Toolbar>
  <FontFamily>
    <FontFamily.Trigger className="acme-font-trigger" />
    <FontFamily.Content>
      <FontFamily.Item value="+mj-lt" />
      <FontFamily.Item value="+mn-lt" />
      <FontFamily.Item value="Arial">Arial (brand)</FontFamily.Item>
    </FontFamily.Content>
  </FontFamily>
</PptxEditor.Toolbar>;

In Vue, use const FontFamily = PptxEditorToolbar.FontFamily and the same parts. Each part takes className. An item whose value names no font renders nothing. A part outside Toolbar.FontFamily renders nothing and prints a development warning.

Build the list from useFontFamily

useFontFamily() gives the picker's behavior with no UI. It returns:

  • value, the family of the selected text, or null for a mixed selection or no selection.
  • theme, the theme font that the text uses (major or minor), or null.
  • options, every family the editor offers.
  • groups, the default picker's groups for the current slide: the theme fonts (theme), the other families the deck uses (document), and all families (all). Each group has an id, a labelKey to translate, and rows. Each row has family, value (a theme font reference on a theme row), roleKey, and disabled.
  • isCurrent(row), which is true for the row of the font that the text uses.
  • setValue(family), which applies a family or a theme font reference. A refusal does nothing.
  • isEnabled and disabledReason, as useEditorCommand gives them.

This list shows the theme fonts and the deck's fonts inside the default trigger and popover:

import { PptxEditor, useFontFamily, useTranslation } from '@pptx-editor.dev/react';

const { FontFamily } = PptxEditor.Toolbar;

function DeckFonts() {
  const { groups } = useFontFamily();
  const { t } = useTranslation();
  return groups
    .filter((group) => group.id !== 'all')
    .map((group) => (
      <div key={group.id} role="group" aria-label={t(group.labelKey)}>
        {group.rows.map((row) => (
          <FontFamily.Item key={row.value} value={row.value} />
        ))}
      </div>
    ));
}

export function DeckFontToolbar() {
  return (
    <PptxEditor.Toolbar>
      <FontFamily>
        <FontFamily.Trigger />
        <FontFamily.Content>
          <DeckFonts />
        </FontFamily.Content>
      </FontFamily>
    </PptxEditor.Toolbar>
  );
}

To build a picker from nothing, replace the whole control with Toolbar.Slot. Render your own list from groups or options, isCurrent, and setValue. See Replace one control. To apply a theme font from code, see Theme fonts.

Place the shape format controls

At 1440 px, the default bar has no room for Quick Styles, Shape Effects, Text Effects, Rotate, Format Shape, and Selection Pane. It leaves them out. The ribbon's Shape Format tab shows them. Place them with their named parts or with Toolbar.Slot. The Vue toolbar draws these controls too.

<PptxEditor.Toolbar>
  <PptxEditor.Toolbar.Group id="shape">
    <PptxEditor.Toolbar.ShapeStyles />
    <PptxEditor.Toolbar.ShapeEffects />
    <PptxEditor.Toolbar.TextEffects />
  </PptxEditor.Toolbar.Group>
  <PptxEditor.Toolbar.Group id="arrange">
    <PptxEditor.Toolbar.Rotate />
  </PptxEditor.Toolbar.Group>
</PptxEditor.Toolbar>
  • Toolbar.ShapeStyles (shape.styles) applies a theme style (p:style) to the selected shapes.
  • Toolbar.ShapeEffects (shape.effects) sets and removes a shadow, reflection, glow, soft edge, bevel, or 3-D rotation. The inner shadow, reflection, glow, soft edge, bevel, and 3-D rotation paint only with the drawing module of @pptx-editor.dev/pro (EigenPal Pro License). Without the module, the menu offers the outer shadows and No shadow, and Toolbar.ImageStyles (image.styles) offers only the frame styles. Removing an effect needs no module. When the selected shapes or pictures have a reflection, glow, soft edge, bevel, or turned camera, the menu also offers No reflection, No glow, No soft edges, No bevel, or No rotation. No shadow also removes an inner shadow. The commands still write every effect, and save keeps it. The controls and the useEditorValueCommand options refresh after the module installs and when the selection or its effects change. A custom control reads the choices with chromeOfferedControl(slot, control, editor). Read them again on each editor change, and after subscribeDrawingRenderers from @pptx-editor.dev/core/layout calls back.
  • Toolbar.TextEffects (shape.textEffects) sets or removes the text warp of the selected shapes from its Transform gallery. A value is transform. and a warp name, such as transform.textArchUp, or transform.none. The gallery sets the warp's default adjust values. To set other adjust values, run the setTextWarp command. See Warp text.
  • Toolbar.Rotate (arrange.rotate) turns the selection 90° or flips it.
  • Toolbar.FormatShape (shape.properties) and Toolbar.SelectionPane (arrange.selectionPane) call requestPane(editor, pane). A mounted side panel turns to its Format or Selection tab. While no side panel is mounted and no listenPaneRequests listener is registered, the controls are disabled with the disabled.noSidePanel reason. See Open the side panel from the toolbar.

Apply a typed value

useEditorValueCommand(slotId) binds a value slot with its type from ToolbarValueMap. A typo in a value does not compile.

import { useEditorValueCommand } from '@pptx-editor.dev/react';

export function RotateRightButton() {
  const rotate = useEditorValueCommand('arrange.rotate');
  return (
    <button
      type="button"
      aria-disabled={!rotate.isEnabled || undefined}
      onMouseDown={(event) => event.preventDefault()}
      onClick={() => {
        const result = rotate.execute('right90');
        if (!result.ok) console.info(result.reason);
      }}
    >
      Rotate right
    </button>
  );
}

In Vue, the members are refs, such as isEnabled.value. useEditorValueCommand('shape.effects').execute('shadow.outerBottomRight') adds a shadow.

MemberUse
execute(value)Runs the command and returns the engine's CommandResult. A refused value gives ok: false and the reason, such as disabled.invalidValue or disabled.noEffectChange.
valueThe value that the slot shows. null when the selection is mixed or the slot shows no value (the highlight, shape fill, line color, line spacing, and Arrange slots).
optionsThe slot's fixed choices from the registry, such as the Rotate, Align, line width, and Shape Effects rows. A slot with a free value, such as font.size, shape.fill, or shape.styles, has none.
isEnabled, disabledReasonAs useEditorCommand gives them.

Numeric slots give numbers: font.size and shape.lineWidth in value and options, and list.lineSpacing in options. shape.effects shows one choice for each effect kind, separated by spaces (shadow.outerTop reflection.none glow.none softEdge.none bevel.none rotation3d.none). Test one choice with chromeOptionChecked(value, choice) from @pptx-editor.dev/core/editor.

Build a control from a command

Build a control from useEditorCommand when Toolbar.Action does not fit. Keep the selection, keep the control focusable when it is disabled, and give the reason.

import { translateKeyOrText } from '@pptx-editor.dev/core/editor';
import { useEditorCommand, useTranslation } from '@pptx-editor.dev/react';

export function PresentButton() {
  const { t } = useTranslation();
  const present = useEditorCommand('view.playFromStart');
  const reason = present.disabledReason;
  return (
    <button
      type="button"
      aria-disabled={!present.isEnabled || undefined}
      title={reason ? translateKeyOrText(t, reason) : undefined}
      onMouseDown={(event) => event.preventDefault()}
      onClick={() => {
        if (present.isEnabled) present.execute();
      }}
    >
      Present
    </button>
  );
}

Render PresentButton inside PptxEditor.Root, and mount PptxEditor.Slideshow in the viewport so the show has a place to play. In Vue, render it inside PptxEditorRoot and mount PptxEditorSlideshow. For the state rules, see Command state.

Build a layout picker

LayoutGallery shows the deck's slide layouts as a Layout or New slide gallery. It has one group for each slide master. Each tile is the engine's own render of a new slide on that layout. Its tiles are menu rows, so you can put the gallery in any menu or popup of your own.

import { LayoutGallery, useEditorCommand, useSlideLayouts } from '@pptx-editor.dev/react';

export function MyLayoutMenu() {
  const layout = useEditorCommand('slide.layout');
  return (
    <div role="menu" aria-label="Layout">
      <LayoutGallery
        value={String(layout.value ?? '')}
        disabled={!layout.isEnabled}
        disabledReason={layout.disabledReason}
        onChange={(id) => layout.execute(id)}
      />
    </div>
  );
}

export function MyNewSectionMenu() {
  const add = useEditorCommand('slide.add');
  const sections = useSlideLayouts().filter((l) => l.type === 'secHead');
  return (
    <div role="menu" aria-label="New section">
      <LayoutGallery layouts={sections} onChange={(id) => add.execute(id)} />
    </div>
  );
}

The Layout menu marks the current layout. The New section menu lists only the section header layouts. Render both inside PptxEditor.Root.

PropDescription
onChangeCalled with the picked layout's id. In Vue, listen to change.
valueThe layout to mark as the current one. Without it, the tiles are plain menu rows.
layoutsThe layouts to show. The default is every layout of the deck.
disabled, disabledReasonShow the tiles but take no pick, and the reason for their tooltips. The default is disabled while the deck is open for viewing.
classNameA class name added to the gallery's root element.

useSlideLayouts() returns the deck's layouts, in gallery order. Each one has id, name, type (the p:sldLayout/@type value, such as title or secHead), masterId, and masterName. The array keeps its identity while the layouts do not change. Run the pick through useEditorCommand. slide.layout changes the layout of the selected slides, and its value is the layout of the current slide. slide.add adds a slide with the layout. For the Vue props, see React and Vue reference.

Limits

  • Hiding a control does not remove its operation. Menus, shortcuts, and the ribbon still run it.
  • Keep the caret when a toolbar button receives a mouse press. Call preventDefault() on mouse down for buttons, but skip inputs, selects, and text areas.
  • A picker has no single element, so asChild on a picker prints a development warning and does nothing.
  • The Vue toolbar draws the same controls as React's. See React and Vue reference.

See also