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} />;
}Create PageSetup.vue:
<script setup lang="ts">
import { DocxEditorPageSetupDialog as Dialog } from '@docx-editor.dev/vue';
import type { DocxEditorPageSetupDialogProps } from '@docx-editor.dev/vue';
const props = withDefaults(defineProps<DocxEditorPageSetupDialogProps>(), {
preset: true,
});
const Apply = Dialog.Apply;
</script>
<template>
<Dialog v-bind="props">
<Apply as-child>
<button class="app-apply">Apply changes</button>
</Apply>
</Dialog>
</template>Keep preset: true when forwarding Vue props; omitted Boolean props otherwise
become false. Register the component in your editor:
<script setup lang="ts">
import { DocxEditor, definePopup } from '@docx-editor.dev/vue';
import type { DocxEditorPopups } from '@docx-editor.dev/vue';
import PageSetup from './PageSetup.vue';
import '@docx-editor.dev/core/styles/editor.css';
defineProps<{ document: ArrayBuffer }>();
const popups: DocxEditorPopups = { pageSetup: definePopup(PageSetup) };
</script>
<template>
<DocxEditor :document="document" :popups="popups" />
</template>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.
| Entry | Supplied props |
|---|---|
pageSetup, paragraph | Controlled dialog props: open, onClose |
textFormField | { session: TextFormFieldDialogSession } |
hyperlink | HyperLinkProps |
contentControl | ContentControlProps |
equation | No state props |
contextMenu | DocxEditorContextMenuProps |
imageProperties | DocxEditorImagePropertiesDialogProps |
imageAltText | DocxEditorImageAltTextPopupProps |
noteProperties | DocxEditorNotePropertiesDialogProps |
notesContextMenu | DocxEditorNotesContextMenuProps |
notePreview | DocxEditorNotePreviewProps |
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>Create TopMargin.vue:
<script setup lang="ts">
import { usePageSetupDialog } from '@docx-editor.dev/vue';
const { values, setValue, isEnabled } = usePageSetupDialog();
function update(event: Event) {
const input = event.target as HTMLInputElement;
setValue('marginTop', Math.round(Number(input.value) * 1440));
}
</script>
<template>
<label>
Top margin (inches)
<input
type="number"
step="0.1"
:value="values.marginTop / 1440"
:disabled="!isEnabled"
@input="update"
/>
</label>
</template>Import TopMargin in PageSetup.vue and declare const Field = Dialog.Field.
Add this named child inside Dialog:
<Field name="marginTop"><TopMargin /></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:
| Token | Controls |
|---|---|
--doc-dialog-font-family | The panel font stack. |
--doc-dialog-font-size | The base size. Titles, labels, and hints scale from it. |
--doc-dialog-radius | The panel corner radius. |
--doc-dialog-control-radius | The corner radius of inputs, text areas, selects, and buttons. |
--doc-dialog-padding | One length: the side inset of the header, body, and footer. |
--doc-dialog-gap | The 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
classNameto 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-editorgives 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.
Chrome slot reference
Reference every editor chrome slot and its React and Vue toolbar part across default, contextual, menu, dialog, and header or footer surfaces.
Zoom and fit to screen
Fit the document to the viewport, control zoom from your UI with useZoom, and let the comments pane shrink the page instead of covering it.