New

docx-editor 1.x has shipped. Vue support, i18n, agents. Read the migration guide →

TutorialMay 19, 2026·4 min read

Vue DOCX editor: add a Word editor to a Vue 3 or Nuxt app

Step-by-step guide to adding a Word (DOCX) editor to a Vue 3 or Nuxt app with @eigenpal/docx-editor-vue. Install, render, load and save .docx, all client-side. Open source, no server.

This is a step-by-step guide to adding a Word document editor to a Vue 3 app. By the end you'll have <DocxEditor> mounted, loading a real .docx file, and saving it back, all in the browser.

@eigenpal/docx-editor-vue is the Vue 3 adapter for docx-editor. It's the same editor that ships as @eigenpal/docx-editor-react, with Vue idioms at the surface (composables, slots, events, kebab-case attributes) and the same OOXML parser underneath. The round-trip is lossless: open a Word file, edit it in your Vue app, reopen in Word with tracked changes, comments, tables, headers, footers, and page layout intact.

Step 1: install

npm install @eigenpal/docx-editor-vue

That one install pulls @eigenpal/docx-editor-core and @eigenpal/docx-editor-i18n transitively. Add @eigenpal/docx-editor-agents later if you want the AI agent toolkit.

Step 2: render the editor

<script setup lang="ts">
import { ref } from "vue";
import { DocxEditor } from "@eigenpal/docx-editor-vue";
import "@eigenpal/docx-editor-vue/styles.css";
 
const buf = ref<ArrayBuffer | null>(null);
</script>
 
<template>
  <DocxEditor :document-buffer="buf" />
</template>

A null buffer mounts an empty document. The component is client-only (ProseMirror reads DOM measurements at mount). On Nuxt 3 and 4, the @eigenpal/nuxt-docx-editor module registers it SSR-safe for you, so there's no <ClientOnly> wrapper to write.

Here is <DocxEditor> running. Edit the text, use the toolbar, or open your own .docx:

Step 3: load a .docx file

document-buffer takes an ArrayBuffer. Fetch a file and assign it. From a URL:

<script setup lang="ts">
import { ref, watchEffect } from "vue";
import { DocxEditor } from "@eigenpal/docx-editor-vue";
import "@eigenpal/docx-editor-vue/styles.css";
 
const buf = ref<ArrayBuffer | null>(null);
 
watchEffect(async () => {
  buf.value = await fetch("/contract.docx").then((r) => r.arrayBuffer());
});
</script>
 
<template>
  <DocxEditor :document-buffer="buf" />
</template>

Or from a file picker, so users open their own documents:

<script setup lang="ts">
import { ref } from "vue";
import { DocxEditor } from "@eigenpal/docx-editor-vue";
 
const buf = ref<ArrayBuffer | null>(null);
 
async function onFile(e: Event) {
  const file = (e.target as HTMLInputElement).files?.[0];
  if (file) buf.value = await file.arrayBuffer();
}
</script>
 
<template>
  <input type="file" accept=".docx" @change="onFile" />
  <DocxEditor v-if="buf" :document-buffer="buf" />
</template>

The editor parses OOXML in the browser. No upload, no server round-trip.

Step 4: save the document

<DocxEditor> exposes an imperative handle. Grab it with useTemplateRef, call save(), and you get an ArrayBuffer of OOXML bytes back:

<script setup lang="ts">
import { useTemplateRef } from "vue";
import { DocxEditor, type DocxEditorRef } from "@eigenpal/docx-editor-vue";
 
const editor = useTemplateRef<DocxEditorRef>("editor");
 
async function save() {
  const buf = await editor.value?.save();
  if (buf) await fetch("/api/documents/1", { method: "PUT", body: buf });
}
</script>
 
<template>
  <button @click="save">Save</button>
  <DocxEditor ref="editor" :document-buffer="buf" />
</template>

The same handle also exposes addComment, proposeChange, findInDocument, scrollToParaId, scrollToPosition, and getDocument, with the same signatures as the React adapter's DocxEditorRef.

That's the whole loop: install, render, load, save. The rest of this post covers the Vue-specific surface (composables, events, slots) and the optional extras (i18n, AI agents).

Composables map one-to-one to React hooks

@eigenpal/docx-editor-vue/composables ships the same set of helpers as @eigenpal/docx-editor-react/hooks, idiomatic to Vue refs.

React (/hooks)Vue (/composables)
useHistoryuseHistory
useFindReplaceuseFindReplace
useAutoSaveuseAutoSave
useClipboarduseClipboard
useTableSelectionuseTableSelection
useDragAutoScrolluseDragAutoScroll
useSelectionHighlightuseSelectionHighlight
useVisualLineNavigationuseVisualLineNavigation
useFixedDropdownuseFixedDropdown
useWheelZoomuseZoom (renamed for Vue convention)
(no equivalent in React)useDocxEditor, useTrackedChanges, useCommentSidebarItems, useTableResize

The Vue exclusives are documented as such in etc/parity.contract.json upstream. The reverse list (React-exclusive composables not yet in Vue) is empty as of 1.0.1.

Parity is checked in CI

A CI step walks the .d.ts of both adapters and diffs their public exports against etc/parity.contract.json. If a symbol ships in one adapter without a counterpart in the other (or a contract entry saying why not), the build fails. Symbols are either paired, marked deferred with a tracking issue, or marked exclusive with a reason. Adding a React export without a Vue sibling doesn't compile in CI.

Events instead of callbacks

React's onFoo callback props become Vue @foo events with the same payload shape.

React propVue event
onChange(doc)@change
onSave(buf)@save
onModeChange(mode)@mode-change
onSelectionChange(state)@selection-change
onCommentAdd(comment)@comment-add
onCommentResolve(comment)@comment-resolve
onCommentDelete(comment)@comment-delete
onCommentReply(reply, parent)@comment-reply
onError(err)@error
onFontsLoaded()@fonts-loaded

Slots instead of render props

React's renderLogo and renderTitleBarRight callback props become Vue named slots:

<DocxEditor :document-buffer="buf" document-name="Q3 Memo">
  <template #titleBarRight>
    <SaveIndicator :dirty="dirty" />
  </template>
</DocxEditor>

i18n with per-locale chunking

<script setup>
import pl from "@eigenpal/docx-editor-i18n/pl";
import { DocxEditor } from "@eigenpal/docx-editor-vue";
</script>
 
<template>
  <DocxEditor :document-buffer="buf" :i18n="pl" />
</template>

Each locale code-splits via the per-locale subpath import. Restored in 1.0.1 after the 1.0 line shipped named-exports-only (which bundled the full 7-locale set into one chunk). Pin to ≥ 1.0.1.

The editor below runs with the Polish locale. Toolbar labels, tooltips, and dialogs are translated:

Agents

import { useDocxAgentTools } from "@eigenpal/docx-editor-agents/vue";

Same 14 tools as the React composable, same OpenAI function-calling schemas, same transport. Server-side wiring through @eigenpal/docx-editor-agents/ai-sdk/server is framework-agnostic. Full client + server setup on Agents → Live editor.

Ask the agent below to review the document. Its comments and tracked changes land in the editor as the model streams tokens:

Where to next