Navigate a document

Build page controls, search, paragraph highlights, and text highlights with the editor API.

These methods belong to the browser Editor API, separate from the Office.js-shaped document model. They scroll, select, and highlight without direct DOM access.

Call useDocxEditor() inside DocxEditor.Root to get the editor instance. React returns an instance or null; Vue returns a shallow ref.

import { useDocxEditor } from '@docx-editor.dev/react';

function GoToEndButton() {
  const editor = useDocxEditor();
  return (
    <button
      disabled={!editor}
      onMouseDown={(event) => event.preventDefault()}
      onClick={() => editor?.scrollToPage(editor.getTotalPages())}
    >
      Go to the last page
    </button>
  );
}

With the core package, use the instance from createDocxEditor(). Prevent the default mousedown action to keep the caret.

MethodPurposeMoves the selection
getTotalPages(), getCurrentPage(mode)Read the page count and the current page.No
scrollToPage(pageNumber)Show a page.No
getOutline()List headings.No
scrollToBlock(blockId)Show a heading or other body paragraph.Only to leave a header, footer, note, or text frame. The body selection returns.
scrollToAnchor(anchor, options)Show a paragraph addressed by its w14:paraId.No
highlightAnchor(anchor, options)Mark a paragraph with a temporary highlight.No
clearAnchorHighlight(options)Dismiss the paragraph highlight.No
findMatches(query, options)Find text in every document region.No
selectMatch(match)Select a match and show it.Yes
exec({ type: 'replaceMatch', match, text })Replace a match where it is.No
setHighlights(name, ranges, options)Mark text ranges, such as glossary terms.No
clearHighlights(name)Remove one set of text highlights, or all of them.No
getHighlightsAt(clientX, clientY)Read the text highlights under a pointer position.No
exec({ type: 'setSelection', anchor })Move the caret to a paragraph reference.Yes

Go to pages, headings, and paragraphs

Page numbers start at one. Pass 'viewport' to getCurrentPage() for the page at the center of the visible area instead of the caret page.

const current = editor.getCurrentPage('viewport');
pageLabel.textContent = `Page ${current} of ${editor.getTotalPages()}`;
editor.scrollToPage(current + 1); // false for a page that does not exist

for (const heading of editor.getOutline()) {
  // Each entry has text, level, and blockId.
  item.onclick = () => editor.scrollToBlock(heading.blockId, { block: 'center' });
}
  • scrollToPage() puts the page top 24 CSS pixels below the viewport top, within scroll limits. It returns false for a page number that is not a positive integer, or when no measurable scroll container exists.
  • Without a measurable viewport, getCurrentPage('viewport') returns the caret page.
  • scrollToBlock() accepts { block: 'start' | 'center' | 'centerIfNeeded' | 'nearest' }, with 'start' as the default. It returns false for an unknown placement.

Block IDs belong to the open document. Read the outline again after you load another document.

Scroll to a paragraph reference

Store a paragraph's paraId with a finding, then pass it as a DocAnchor.

const findings = editor
  .query({ type: 'paragraphs' })
  .filter((paragraph) => paragraph.paraId && paragraph.text.includes('indemnify'))
  .map((paragraph) => ({ paraId: paragraph.paraId!, label: paragraph.text }));

const revealed = editor.scrollToAnchor({
  paraId: '1B4C77A2',
  search: 'Acme GmbH', // optional phrase inside the paragraph
  occurrence: 2,
});
if (!revealed) {
  // Tell the user that the reference is unavailable.
}

The method preserves selection, focus, document content, and undo history. It also works in viewing mode. It completes a pending document mount and commits pending typing before it resolves the anchor.

Anchor fieldRule
paraIdRequired. Matching ignores case.
searchOptional. Exact, case-sensitive match inside the paragraph.
occurrencePositive, one-based match number. Without it, search must match exactly once.
scrollToAnchor() resultMeaning
trueThe target is visible, or the editor scrolls to it.
falseThe anchor is invalid, missing, ambiguous, unsupported, or has no layout position. The editor may also have no mounted document or scroll container.

Pass ScrollToAnchorOptions as the second argument to control the position.

editor.scrollToAnchor({ paraId: '1B4C77A2' }, { block: 'start', behavior: 'smooth', offsetPx: 48 });
OptionDefaultDescription
block'centerIfNeeded''start', 'center', 'centerIfNeeded', or 'nearest'. 'centerIfNeeded' does not scroll a visible target.
behavior'instant''smooth' animates. Reduced-motion preferences make it instant.
offsetPx24Nonnegative edge padding for 'start' and 'nearest', in CSS pixels.

An invalid block or behavior throws TypeError. An invalid offsetPx throws RangeError. The method returns when scrolling starts.

Move the caret to a reference

Run setSelection with the same anchor, then focus the editor.

const result = editor.exec({
  type: 'setSelection',
  anchor: { paraId: '1B4C77A2', search: 'Acme GmbH' },
});
if (result.ok) editor.focus();

The caret goes to the start of the paragraph or the matched text.

Search and replace text

findMatches() returns matches without moving the selection. selectMatch() selects one and returns an ExecResult.

const [first, second] = editor.findMatches('Supplier', { matchCase: true, wholeWord: true });
if (first && editor.selectMatch(first).ok) status.textContent = first.text;

if (second) {
  const result = editor.exec({ type: 'replaceMatch', match: second, text: 'Vendor' });
  if (!result.ok) console.warn(result.reason);
}

selectMatch() opens a header, footer, or note when needed and does not move focus.

replaceMatch keeps the caret, selection, and scroll position. An empty text deletes the match. In suggesting mode, it records a tracked deletion and insertion with one undo step, and it refuses edits that cannot be tracked.

Search from code

The Find tab, useDocumentSearch(), and createDocumentSearch(editor) share one search session per editor.

import { useDocumentSearch } from '@docx-editor.dev/react';

function ShowOccurrences({ term }: { term: string }) {
  const search = useDocumentSearch();

  return (
    <>
      <button onClick={() => search.find(term, { selectFirst: true })}>Show “{term}”</button>
      <button onClick={search.previous}>Previous</button>
      <button onClick={search.next}>Next</button>
      <span>
        {search.activeIndex + 1} of {search.matches.length}
      </span>
    </>
  );
}

Without an adapter, call createDocumentSearch(editor) from @docx-editor.dev/core/editor, and read getState() in a subscribe() listener.

MemberDescription
find(query, options)Searches now and returns the matches. Options: matchCase, wholeWord, selectFirst, and highlight. Replaces the previous find() highlight request.
setQuery(query)Searches after 150 milliseconds without query changes. isPending reports the wait.
next(), previous(), goTo(index)Applies pending document changes to the results, then selects a match and scrolls to it. Focus does not move.
clear()Clears the query, results, and highlight request.
matches, activeIndex, activeMatchThe results and the current match. Edits refresh the results after 150 milliseconds without edits, one second maximum.
truncatedtrue when the result cap was reached.

highlight is 'all', 'active', or 'none'. useDocumentSearch() highlights all results while mounted; pass { highlight: 'none' } to only read results. When requests differ, 'all' wins over 'active', and 'none' does not hide another component's highlights.

Highlight a paragraph reference

Call highlightAnchor() after scrollToAnchor() to mark the referenced paragraph.

import type { DocAnchor, Editor } from '@docx-editor.dev/core';

function showReference(editor: Editor, anchor: DocAnchor): boolean {
  if (!editor.scrollToAnchor(anchor)) return false;
  return editor.highlightAnchor(anchor, { timeoutMs: 1500 });
}

The highlight marks the whole paragraph, even when search validates a phrase. Each call replaces the previous highlight. Local edits keep the highlight on its paragraph.

highlightAnchor() resultMeaning
trueThe paragraph has a body layout position, even offscreen.
falseThe paragraph has no body layout position. The current highlight stays unchanged.
OptionDefaultDescription
colorvar(--doc-anchor-highlight-color)CSS color or variable.
opacity0.14Fill opacity between 0 and 1.
padding4Extra space on each edge, in CSS pixels at 100% zoom.
borderRadius6Corner radius in CSS pixels at 100% zoom.
borderWidth0Border width in CSS pixels at 100% zoom.
borderColorSame as colorCSS border color, independent of fill opacity.
borderStyle'solid''solid', 'dashed', or 'dotted'.
classNameOmittedCSS classes for shadows, outlines, or patterns.
timeoutMs3000Milliseconds before dismissal. null keeps the highlight until you clear it.
animationtrueA 180-millisecond fade. false changes at once. An object sets durationMs (0 to 10000), exitDurationMs, and easing; CSS variables are rejected for easing. exitDurationMs defaults to durationMs, and easing defaults to --doc-motion-ease-out.

RefreshHighlightOptions extends these options with a different default color token. For refresh-specific options, see Customize highlights and motion.

editor.highlightAnchor(anchor, { timeoutMs: null });
editor.clearAnchorHighlight({ animation: false });
  • Without overrides, clearAnchorHighlight() uses the exit duration and easing of the last highlight. It validates the options you pass even when no highlight exists.
  • Anchor highlights and refresh highlights are independent. Clearing one does not clear the other.

Invalid numeric options throw RangeError, even when the paragraph does not exist. Invalid colors, border styles, or animation settings throw TypeError. To change the default color, set --doc-anchor-highlight-color on .docx-editor.

Highlight text ranges

Highlight your own ranges as a named set. Pass a function to recompute ranges after document changes.

import { useCallback } from 'react';
import { useHighlights, type Editor } from '@docx-editor.dev/react';

function Glossary({ terms }: { terms: readonly string[] }) {
  const findTerms = useCallback(
    (editor: Editor) => editor.findMatches(terms, { wholeWord: true }).flat(),
    [terms]
  );
  useHighlights('glossary', findTerms, { color: 'var(--glossary-color)' });
  return null;
}

Without an adapter, call watchHighlights(editor, name, source) from @docx-editor.dev/core/editor, and call stop() on the result. For a one-time set, call editor.setHighlights('findings', matches). findMatches() with an array returns one list per term.

OptionDefaultDescription
colorvar(--doc-text-highlight-color)CSS color for every range.
activeIndex-1Index of the active range. -1 means none.
activeColorvar(--doc-text-highlight-active-color)CSS color for the active range.
classNameNoneCSS classes for every highlight.
priority0Integer from -1000 to 1000. Higher sets paint on top.

setHighlights() returns { applied, unavailable }. Invalid input throws TypeError or RangeError and keeps the existing set.

  • Custom ranges take blockId, start, and length in UTF-16 units.
  • Add expectedText to ranges computed outside the editor.

Each set renders in a [data-highlight-set] element. Highlights have the docx-text-highlight class; the active one also has docx-text-highlight--active.

Show details on hover

Add your own fields to each range. useHighlightAt(name) returns the highlight under the pointer, with your range and a rect in client coordinates.

import { useHighlightAt, type TextMatch } from '@docx-editor.dev/react';

type GlossaryRange = TextMatch & { readonly definition: string };

function GlossaryTooltip() {
  const hit = useHighlightAt<GlossaryRange>('glossary');
  if (!hit) return null;
  return (
    <div role="tooltip" style={{ position: 'fixed', left: hit.rect.left, top: hit.rect.bottom }}>
      {hit.range.definition}
    </div>
  );
}

Without an adapter, call editor.getHighlightsAt(clientX, clientY) from a pointermove handler.

Configure navigation shortcuts

Set shortcut and highlight props on the packaged navigation prop or a composed pane.

<DocxEditor document={file} navigation={{ findShortcut: false }} />

<DocxEditor.Navigation findShortcut={false} searchHighlight="active" />

Open Find with Ctrl+F

PropDescription
findShortcutfalse leaves Ctrl+F (Cmd+F on macOS) to the browser.
goToShortcutfalse leaves Ctrl+G (Cmd+Option+G on macOS) to the browser. It opens the Bookmarks tab.
searchHighlight'all', 'active', or 'none'. Sets which Find matches are highlighted.
onTabChange, onOpenChangeControlled-pane callbacks. A shortcut calls onTabChange('find') and onOpenChange(true). The pane gets focus only if your state opens it in the same update.

The shortcuts work only while focus is in the same editor: its pages, pane, or chrome inside its container. Chrome outside a shared container counts only when the page has one editor. Elsewhere, the browser handles them.

Limits

  • Paragraph IDs come from the open document. Call editor.load(bytes) before you scroll to an ID from fetched bytes.
  • scrollToAnchor() supports body, table cells, block content controls, headers, footers, footnotes, and endnotes. Repeated headers use their first occurrence. Text boxes return false.
  • highlightAnchor() supports body, table cells, and block content controls only.
  • Search caps results at 2,000. When truncated is true, show the count as a minimum, such as 2000+.
  • Viewing mode searches body stories only. Editing mode also searches headers, footers, notes, and text boxes.
  • Matches go stale after document changes. Search again.
  • replaceMatch requires the match's story to be the active one. A text-box match returns unsupported.
  • Text highlights allow 32 sets and 10,000 ranges per set. Ranges past the limit count as unavailable and do not throw. The name search is reserved.
  • Text highlights support the body, tables, headers, footers, notes, and text boxes in the body.
  • Highlights are not saved or shared, and they do not change selection or undo history. Give each one a text label.
  • A text highlight moves with its text when you edit earlier text. An edit inside a highlighted range hides it; undo shows it again. Loading, refreshing, or recovering the document removes all highlights.
  • Function sources recompute after edits (150 milliseconds idle, one second maximum) and loads. Fixed arrays return after refresh or recovery, not after an ordinary load.

See also