Customize popups

Use your components to customize editor popups in React and Vue.

Pass popups to the editor or Root. Use definePopup(MyPopup) to supply a React component or Vue single-file component. The editor opens it and supplies its typed props. Forward those props to the default component to retain its state, validation, and commands.

Replace a button

These examples replace Apply in the Page Setup dialog. The editor manages the opening state and session.

import { DocxEditor, definePopup } from '@docx-editor.dev/react';
import type { DocxEditorPageSetupDialogProps, DocxEditorPopups } from '@docx-editor.dev/react';
import '@docx-editor.dev/core/styles/editor.css';

function PageSetup(props: DocxEditorPageSetupDialogProps) {
  return (
    <DocxEditor.PageSetupDialog {...props}>
      <DocxEditor.PageSetupDialog.Apply asChild>
        <button className="app-apply">Apply changes</button>
      </DocxEditor.PageSetupDialog.Apply>
    </DocxEditor.PageSetupDialog>
  );
}

const popups: DocxEditorPopups = { pageSetup: definePopup(PageSetup) };

export function Editor({ document }: { document: ArrayBuffer }) {
  return <DocxEditor document={document} popups={popups} />;
}

Custom button components must forward refs, attributes, and listeners to their native button. Preserve the supplied handler and disabled state.

asChild forwards behavior, positioning, data attributes, and your className to the child element. It omits the packaged decorative classes. To adjust the default appearance, pass className to the part without asChild.

The hyperlink popup's Root keeps its packaged class for positioning and stacking. Use a scoped .docx-hyperlink-popup rule to override its appearance.

Render callbacks also work: pageSetup: (props) => <PageSetup {...props} /> in React, or pageSetup: (props) => h(PageSetup, props) in Vue. Call hooks inside components, not render callbacks.

Choose a popup

Each entry accepts a definePopup() result, render callback, or false. Each popup receives the props listed here.

EntrySupplied props
pageSetup, paragraphControlled dialog props: open, onClose
textFormField{ session: TextFormFieldDialogSession }
hyperlinkHyperLinkProps
contentControlContentControlProps
equationNo state props
contextMenuDocxEditorContextMenuProps
imagePropertiesDocxEditorImagePropertiesDialogProps
imageAltTextDocxEditorImageAltTextPopupProps
notePropertiesDocxEditorNotePropertiesDialogProps
notesContextMenuDocxEditorNotesContextMenuProps
notePreviewDocxEditorNotePreviewProps
contentControlWidget{ session: ContentControlWidgetSession }
invalidTextFormField{ session: InvalidTextFormFieldSession }

Omitted entries retain existing behavior, including native widget and invalid-field dialogs. The packaged editor supplies defaults. Root mounts configured surfaces; image and note overrides use their existing triggers. Explicit entries override menu.onPageSetup, hyperlinkPopup, and contextMenu shortcuts.

To manage opening and closing yourself, set the entry to false and mount the popup. For component customization with automatic opening, use definePopup(). Toolbar menus and pickers use their existing compound .Content parts, outside this map.

Arrange parts and custom fields

Page Setup, Paragraph Options, and Field Options expose Header, Title, Body, Footer, Apply, Cancel, Error, and typed Field parts. Parts accept className, style, hidden, and asChild. Other popups retain their existing parts.

Parts forward attributes such as id, aria-*, and data-* to the rendered element. Built-in behavior takes precedence: passing onClick to Apply does not replace its handler. Use asChild for a custom element, and preserve the supplied behavior.

Named children replace default parts. Use hidden to remove a part or preset={false} to supply the complete arrangement. Include a title, error region, and accessible Apply and Cancel controls. Omitted fields retain their draft values.

Page Setup's pageSize part is its size selector. Paragraph's tabStops part contains the collection control; specialBy appears when relevant.

For custom value controls, call usePageSetupDialog(), useParagraphDialog(), or useTextFormFieldDialog() inside a child of the matching dialog. Each supplies values, setValue(name, value), errors, isEnabled, apply(), and cancel(). Vue exposes state as refs. Use these draft APIs instead of creating another form session.

A Field represents the labeled row; asChild does not adapt input value events. Page Setup dimensions use twips: 1,440 per inch. Paragraph also exposes mixed-selection state. See React hooks or Vue composables for types.

Connect a custom input

Put the input inside a named Field. Call the draft hook in the input component, which renders under the dialog's provider. This example edits the top margin in inches.

import { usePageSetupDialog } from '@docx-editor.dev/react';

function TopMargin() {
  const { values, setValue, isEnabled } = usePageSetupDialog();
  return (
    <label>
      Top margin (inches)
      <input
        type="number"
        step="0.1"
        value={values.marginTop / 1440}
        disabled={!isEnabled}
        onChange={(event) => setValue('marginTop', Math.round(Number(event.target.value) * 1440))}
      />
    </label>
  );
}

Add this named child inside your PageSetupDialog:

<DocxEditor.PageSetupDialog.Field name="marginTop">
  <TopMargin />
</DocxEditor.PageSetupDialog.Field>

Keep the default Apply and Error parts. Invalid margins keep the dialog open and show an error. Cancel discards the draft.

Style popups

Import your stylesheet after the core stylesheet. Default dialogs have a plain panel, bordered inputs, a filled primary button, and an outlined secondary button. Dialogs inherit --doc-* colors, so a dark editor gets a dark dialog.

Optional tokens adjust the panel without replacing any part:

TokenControls
--doc-dialog-font-familyThe panel font stack.
--doc-dialog-font-sizeThe base size. Titles, labels, and hints scale from it.
--doc-dialog-radiusThe panel corner radius.
--doc-dialog-control-radiusThe corner radius of inputs, text areas, selects, and buttons.
--doc-dialog-paddingOne length: the side inset of the header, body, and footer.
--doc-dialog-gapThe vertical distance between rows and sections.

Use data-docx-dialog values pageSetup, paragraph, or textFormField. data-docx-part uses lowercase part names; fields also expose data-docx-field.

There are two ways to change how a part looks:

  • To adjust the packaged part, pass className to the part. Every default rule uses at least two classes, such as .docx-editor .docx-dialog__button, so it survives element resets like Tailwind preflight. To override these rules, add a class or attribute the default does not have, and skip !important. A selector with one class, such as .app-rounded, has lower specificity. Adding .docx-editor gives equal specificity, so stylesheet order determines the result.
  • To bring your own element, pass asChild. The packaged classes stay off your element, so a single class or utility classes style it as written.
/* className="app-rounded" on the packaged Apply part. The
   data-docx-dialog attribute is the extra compound that wins. */
.docx-editor [data-docx-dialog='pageSetup'] .app-rounded {
  border-radius: 999px;
  padding-inline: 1.25rem;
}

/* asChild with <button class="app-apply">, as in the example above */
.app-apply {
  border: 0;
  border-radius: 999px;
  padding: 8px 20px;
  background: var(--doc-primary);
  color: var(--doc-on-primary);
}

Automatic dialogs inherit their editor's theme. For external portals or Vue Teleport, provide the theme at the destination; framework context does not transfer CSS inheritance. Preserve labels, keyboard behavior, and input-method composition on custom controls.

For a complete session renderer, follow its abort signal and use its supplied actions. Widget sessions validate through apply(value). Invalid-field sessions use acknowledge() to handle the invalid fill, or cancel() to dismiss without clearing it. locale controls regional dates; i18n controls UI strings.

Next steps

Run the React example or Vue example with ?dialogs=1 to try two themed editors with custom dialog and link controls.

On this page