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

On this page