To edit Word documents in a Vite app, install @docx-editor.dev/react, pass the document bytes, and render <DocxEditor>. Vite has no server and client module boundary, so the editor mounts without extra configuration.
Live demo
Open your own .docx, or edit the sample document in the demo. The demo does not upload anything. The document stays in your browser.
Install
npm install @docx-editor.dev/react @docx-editor.dev/core@docx-editor.dev/core is a peer dependency. The adapter bundles the string catalog.
Build the editor component
import { useState, useEffect, useRef, useCallback } from "react";
import { DocxEditor } from "@docx-editor.dev/react";
import type { DocxEditorRef } from "@docx-editor.dev/react";
import "@docx-editor.dev/core/styles/editor.css";
const DOCX_MIME =
"application/vnd.openxmlformats-officedocument.wordprocessingml.document";
export function MyDocxEditor() {
const editorRef = useRef<DocxEditorRef>(null);
const [buffer, setBuffer] = useState<ArrayBuffer | null>(null);
useEffect(() => {
fetch("/sample.docx")
.then((res) => res.arrayBuffer())
.then(setBuffer);
}, []);
const handleSave = useCallback(async () => {
const saved = await editorRef.current?.save();
if (!saved) return;
const blob = new Blob([saved], { type: DOCX_MIME });
const url = URL.createObjectURL(blob);
Object.assign(document.createElement("a"), {
href: url,
download: "edited.docx",
}).click();
URL.revokeObjectURL(url);
}, []);
if (!buffer) return <div>Loading...</div>;
return (
<div style={{ height: "80vh" }}>
<DocxEditor ref={editorRef} document={buffer} mode="edit" />
<button onClick={handleSave}>Download .docx</button>
</div>
);
}Vite needs no "use client" directive. It has no server and client module boundary, so the component mounts as it is.
Render the component
import { MyDocxEditor } from "./components/DocxEditor";
function App() {
return <MyDocxEditor />;
}
export default App;Let users open a file
import { useState } from "react";
import { DocxEditor } from "@docx-editor.dev/react";
export function UploadEditor() {
const [buffer, setBuffer] = useState<ArrayBuffer | null>(null);
function handleUpload(event: React.ChangeEvent<HTMLInputElement>) {
const file = event.target.files?.[0];
if (!file) return;
const reader = new FileReader();
reader.onload = () => setBuffer(reader.result as ArrayBuffer);
reader.readAsArrayBuffer(file);
}
return (
<div style={{ height: "80vh" }}>
<input type="file" accept=".docx" onChange={handleUpload} />
{buffer && <DocxEditor document={buffer} />}
</div>
);
}Set a different buffer to load another document without a page reload.
Save to a backend
import { useRef } from "react";
import { DocxEditor, type DocxEditorRef } from "@docx-editor.dev/react";
export function SavingEditor({ buffer }: { buffer: ArrayBuffer }) {
const editorRef = useRef<DocxEditorRef>(null);
async function save() {
const saved = await editorRef.current?.save();
if (!saved) return;
await fetch("https://your-api.com/documents", {
method: "POST",
body: saved,
});
}
return (
<>
<button onClick={save}>Save</button>
<DocxEditor ref={editorRef} document={buffer} />
</>
);
}save() returns Promise<ArrayBuffer | null>. It returns null when no document is open.
Common errors
| Error | Fix |
|---|---|
| Styles do not render | Import @docx-editor.dev/core/styles/editor.css in your component |
| Blank editor | Load the ArrayBuffer before you render <DocxEditor> |
Included features
The editor parses OOXML in the browser and renders the document as pages. It supports:
- Bold, italic, underline, and strikethrough
- Tables, including merged cells
- Inline and floating images
- Headers, footers, and page breaks
- Zoom and a document outline
It writes the document back to .docx. The editor is Apache 2.0 and needs no document-processing server. For more information about tracked changes, comments, and custom nodes, see the Pro module.
Where to go next
- Vite setup reference covers the same steps in the docs.
- Vite example on GitHub is a runnable project.
- Tracked changes and Comments cover review workflows.
- React props reference documents every prop.