Composables
The Vue API the packaged chrome is built on: subscribe to editor state, run commands, read the document outline, search, and drive page setup.
Call each composable during setup() inside DocxEditor.Root. The packaged
<DocxEditor> also renders this root.
These composables return a ref, or a plain object that holds refs. A template
unwraps a ref only when the ref is a top-level binding. A ref that sits on a
returned object stays a ref, so read it with .value in the template and in
script code.
useEditorCommand
Pass a chrome slot ID to get its action and reactive command state:
<script setup lang="ts">
import { useEditorCommand } from '@docx-editor.dev/vue';
const bold = useEditorCommand('text.bold');
</script>
<template>
<button
@mousedown.prevent
:disabled="!bold.isEnabled.value"
:data-active="bold.isActive.value || undefined"
:title="bold.disabledReason.value ?? 'Bold'"
@click="bold.execute()"
>
B
</button>
</template>execute() returns true when the editor applies the command. isActive,
isEnabled, and disabledReason are computed refs on a plain object. The
template does not unwrap them, so read each one with .value.
The engine is the only source for enabled state. Show disabledReason when a
control cannot run.
You can also pass a reactive slot ID or an EditorCommand:
import { computed } from 'vue';
import { useEditorCommand } from '@docx-editor.dev/vue';
const mode = computed(() => ({
type: 'setEditingMode' as const,
mode: 'suggesting' as const,
}));
const suggest = useEditorCommand(mode);useEditorState
Subscribe to one slice of the editor snapshot. The composable returns a read-only shallow ref.
<script setup lang="ts">
import { useEditorState } from '@docx-editor.dev/vue';
const page = useEditorState((s) => s.page);
const dirty = useEditorState((s) => s.canUndo ?? false);
</script>
<template>
<span>{{ page.current }} / {{ page.total }}</span>
<button :disabled="!dirty">Save</button>
</template>The selector runs after editor updates. The ref changes only when the selected value changes.
Pass a comparator when you select an object:
import { useEditorState } from '@docx-editor.dev/vue';
const formatting = useEditorState(
(snapshot) => snapshot.formatting,
(left, right) => left?.bold === right?.bold && left?.italic === right?.italic
);Select the smallest useful value. A page selector then stays stable when the user changes bold formatting.
Useful snapshot fields include page, selection, selectionCollapsed,
formatting, table, image, editable, isLoading, isOpening,
parseError, editingMode, canUndo, canRedo, pageSetup,
fontSubstitutions, hasReviewContent, and lastRejection.
useDocxEditor
This composable returns a shallow ref to the editor instance. Its value is
null before the content mounts.
<script setup lang="ts">
import { useDocxEditor } from '@docx-editor.dev/vue';
const editor = useDocxEditor();
async function save() {
const bytes = await editor.value?.save();
if (bytes) void upload(bytes);
}
</script>Use the instance for actions and one-time reads. A direct snapshot() call
does not create a Vue subscription. Use useEditorState for reactive state.
useEditorEvent
Subscribe to an editor event for the component lifetime:
import { useEditorEvent } from '@docx-editor.dev/vue';
useEditorEvent('selectionChange', () => {
panelOpen.value = false;
});
useEditorEvent('change', (change) => {
void autosave(change.revision);
});useEditorCaret
Returns the caret as { paragraphId, offset }. Write APIs accept this shape as
their at value.
useEditorSnapshot
Returns a revision ref for an editor. Use it for custom external-store wiring.
useZoom
Returns the current zoom and controls that change it.
useFontFamily
Returns the current family, available families, setter, and enabled state:
<script setup lang="ts">
import { useFontFamily } from '@docx-editor.dev/vue';
const font = useFontFamily();
</script>
<template>
<select
:value="font.value.value ?? ''"
:disabled="!font.isEnabled.value"
@change="font.setValue(($event.target as HTMLSelectElement).value)"
>
<option v-for="family in font.options.value" :key="family" :value="family">
{{ family }}
</option>
</select>
</template>The current family sits on the value member, and that member is a computed
ref. Read it as font.value.value.
useParagraphStyle provides the same pattern for paragraph styles.
usePageSetup
Read and change page size, margins, orientation, and scope:
<script setup lang="ts">
import { computed } from 'vue';
import { usePageSetup } from '@docx-editor.dev/vue';
const page = usePageSetup();
const landscape = computed(() => page.pageSetup.value?.orientation === 'landscape');
function toggleOrientation() {
page.apply({
orientation: landscape.value ? 'portrait' : 'landscape',
scope: 'section',
});
}
</script>
<template>
<button :disabled="!page.isEnabled.value" @click="toggleOrientation">
{{ landscape ? 'Use portrait' : 'Use landscape' }}
</button>
</template>apply() returns true when the editor applies the update. Sizes and margins
use twips. Set scope to 'section' or 'document'.
useParagraphIndent
Returns current paragraph indents and an apply() action for ruler controls.
useDocumentOutline
Read headings in document order and move the selection to a heading:
<script setup lang="ts">
import { useDocumentOutline } from '@docx-editor.dev/vue';
const outline = useDocumentOutline();
</script>
<template>
<p v-if="outline.isEmpty.value">No headings</p>
<ul v-else>
<li
v-for="{ heading, depth } in outline.items.value"
:key="heading.blockId"
:style="{ paddingInlineStart: `${depth * 12}px` }"
>
<button
:aria-current="heading.blockId === outline.selectedBlockId.value ? 'location' : undefined"
@click="outline.goTo(heading.blockId)"
>
{{ heading.text }}
</button>
</li>
</ul>
</template>Each item has { heading, depth }. A heading has text, level, and
blockId. depth starts at zero for the shallowest heading in the document.
Use headings when you do not need the calculated depth.
useDocumentSearch
Search the document and move between matches:
<script setup lang="ts">
import { useDocumentSearch } from '@docx-editor.dev/vue';
const search = useDocumentSearch();
</script>
<template>
<label>
Find
<input
:value="search.query.value"
@input="search.setQuery(($event.target as HTMLInputElement).value)"
/>
</label>
<span aria-live="polite">
{{ search.matches.value.length === 0 ? 0 : search.activeIndex.value + 1 }}
/ {{ search.matches.value.length }}{{ search.truncated.value ? '+' : '' }}
</span>
<button :disabled="search.matches.value.length === 0" @click="search.previous">Previous</button>
<button :disabled="search.matches.value.length === 0" @click="search.next">Next</button>
</template>The composable waits 150 milliseconds before it searches. isPending reports
that delay. It also provides goTo, clear, matchCase, setMatchCase,
wholeWord, and setWholeWord. truncated becomes true at the 2,000-match
limit.
useNavigationPane
Returns the navigation pane state, width, active tab, and setters.
useNavigationShift
Returns the horizontal page offset caused by the open navigation pane.
useReviewGutter
Returns the inline reservations for the active review rail. The full card column changes to balanced marker strips on narrow viewports.
useDocxSource
Fetch bytes and fonts from a URL, File, or Blob:
const { document, fonts, error, isLoading } = useDocxSource('/sample.docx', {
fonts: defaultFonts,
});useFonts
Builds a FontResolver from a source and configuration fragments. See the
font guide.
useHyperlinkPopup and useHyperlinkPopupInstance
Returns the link popover state and actions. Use
useHyperlinkPopupInstance outside the component that owns the popover
context.
useContentControl and useContentControlInstance
Returns content-control locks, value actions, and form-fill state. Use
useContentControlInstance outside the component that owns this context.
useHeaderFooterState
Returns the active header or footer scope, or null.
useNoteScopeState and useNotePropertiesState
Return the active note scope and its numbering properties.
useContextMenuTarget
Returns the element that received the last context-menu action.
useEditorValueCommand
Returns state and actions for value commands such as 'image.wrap' and
'image.altText'.
useTableBorderTargetLabel
Returns the label for the active table border target.
useTranslation and useChromeTranslate
useTranslation returns { t } for the active locale. useChromeTranslate
resolves labels for chrome t props. Pass a Map to override selected labels.
Toolbar helpers
useToolbarContext returns the toolbar compound context.
useToolbarLabel returns the active scope label. useToolbarLabelFor resolves
a label for one slot ID. useScopeClassName returns the chrome class prefix.
useScopedChromeAnchor returns anchor data for overlay chrome.
Review author styles
useReviewAuthors returns a shallow ref with each review author's author,
slot, color, and resolved style. Tracked-change authors come first.
Comment-only authors follow them.
Declare styles inside DocxEditor.Root:
<script setup lang="ts">
import { DocxEditor, useReviewAuthors } from '@docx-editor.dev/vue';
const authors = useReviewAuthors();
</script>
<template>
<DocxEditor.AuthorStyle
author="Ada"
color="var(--doc-review-author-1)"
avatar-url="/avatars/ada.png"
/>
<p>{{ authors.length }} review authors</p>
</template>Use DocxEditor.ColorByChangeType to color insertions and deletions by change
type. These components change presentation only. They do not change the DOCX
author data.
Pro Vue composables
Install @docx-editor.dev/pro and import these APIs from
@docx-editor.dev/pro/vue:
useReview()returns items, active state, review actions, pane state, and readiness.useReviewOf(editorRef, query?)binds the same state to an explicit editor ref.useReviewItem()returns the item for the active review card context.useReviewAuthor(author)returns the resolved author style in a review rail.useStackedReviewPositions(items, heights, options?)calculates card positions without overlap.
The package also exports DocxEditorReview. Mount it inside
DocxEditor.Viewport, beside DocxEditor.Content.
Next steps
- Vue composition: place custom controls.
- Toolbar guide: find command slot IDs.
- Vue API reference: inspect all composable types.