Skip to Content
Accessibility

Accessibility

The editor is a composite widget: a tab strip, a ribbon, a document canvas and a status bar, plus optional side panels. Keyboard users move between those regions with F6, and within the current one with arrows or Tab.

Moving between regions

KeyAction
F6 or Ctrl+F6 (Cmd+F6 on macOS)Next region, in document order: toolbar, side panels, document, comments, status bar, then open floating menus
Shift+F6 or Ctrl+Shift+F6Previous region
EscapeFrom the document, go to the toolbar; from the toolbar or status bar, return to the document
Ctrl+/ (Cmd+/)Open the keyboard shortcut reference, from any region

Entering the toolbar lands on the selected tab. Other regions resume on the control you last used there.

Regions that aren’t rendered or have nothing focusable (a closed side panel, a hidden status bar) drop out of the cycle.

Ctrl+F6 exists because browsers reclaim plain F6 for their own panes once focus leaves the page. Word for the web and Google Docs use it for the same reason.

Escape yields to whatever is already using it. If the format painter is armed or the search bar is open, Escape dismisses that instead of moving focus, and inside a comment, footnote or header/footer editor it closes that editor. Only when nothing else claims it does it move focus to the toolbar.

The toolbar

The ribbon is a single Tab stop, following the WAI-ARIA toolbar pattern . One Tab enters it, one Tab leaves it, and arrows move between every control inside it, across group boundaries. Groups are labelled role="group" elements, so a screen reader names each group as you enter it.

KeyAction
/ Previous / next control, wrapping at the ends
Home / EndFirst / last control in the ribbon
TabLeave the ribbon entirely

Inside a text field (font size, row height, column width) the arrows belong to the caret. Once the caret reaches the start or end of the value, the next press in that direction moves to the neighbouring control.

Tabbing back into the ribbon lands on the control you last used, not the first one. Disabled controls (disabled or aria-disabled) are skipped.

Tabs

KeyAction
/ Previous / next tab (the ribbon follows immediately)
Home / EndFirst / last tab
Move down into the ribbon without changing tab

The document

KeyAction
Tab / Shift+TabIndent / outdent — not focus movement
Shift+F10 or the Menu keyOpen the context menu at the caret
F6, Ctrl+F6 / EscapeLeave the document (see above)

Because Tab indents, the document would otherwise be a keyboard trap. The editing surface advertises its exits with aria-keyshortcuts="F6 Control+F6" and an aria-describedby hint (“Press F6 to move to the toolbar, or Control plus slash for all shortcuts”), which satisfies WCAG 2.1.2 No Keyboard Trap .

The context menu opens at the caret, not at the pointer’s last position, and Escape closes it and returns focus to the document.

The ruler is pointer-only and aria-hidden. Keyboard users set the same values with Indent/Outdent and the Page tab’s margin controls.

Modes and read-only

The mode ("edit" | "review" | "view") is announced, not just enforced.

In "view" the document surface keeps role="textbox" and adds aria-readonly="true", so a screen reader says “read only” instead of offering an edit affordance that swallows input. It also gets tabindex="0", because a non-editable element is not focusable and F6 could not reach it otherwise.

Mode changes are spoken through a visually hidden role="status" region: “Editing mode”, “Suggesting mode”, “Viewing mode”. A change can disable or re-enable the whole ribbon, and this is what tells a screen-reader user why. The mode the editor mounts with is not announced, only later changes, whether they come from the prop, useSetEditorMode or the status bar.

The status-bar mode picker names the current mode in its trigger’s accessible name and marks it as checked in the menu. Suggesting is offered only when the track-changes plugin is mounted, or when the editor is already in review.

In view mode, reading controls keep working: footnote and endnote anchors stay operable, and “View Footnotes” / “View Endnotes” stay enabled because their panel is read-only. Controls that would change the document are disabled or removed.

Disabled semantics

Two conventions coexist, both valid under the APG :

  • Ribbon controls use native disabled. They drop out of the tab order and out of the toolbar’s arrow rail, so arrowing across the ribbon never lands on something that cannot act.
  • Menu items use aria-disabled. They stay focusable, so a keyboard user browsing a menu can still read every option and hear which are unavailable.

A split button follows its trigger’s context: the trigger is natively disabled when nothing inside the menu can run, rather than opening onto a menu whose every item is unavailable.

Language

Screen readers switch voice and pronunciation on the lang attribute, so the editor emits it at three levels:

  • the editor chrome (ribbon, panels, status bar) carries the UI locale you pass as locale;
  • the document root carries the document language (doc.attrs.language, "en-GB" by default), so content is never announced in the UI’s language;
  • a paragraph or heading whose effective language differs from the document’s carries that language itself — whether it says so through proofingLanguage or inherits it from its paragraph style’s language.

Only deviations are emitted. proofingLanguage defaults to null (“inherit”) and styles set no language unless a template or a DOCX import gives them one, so in a single-language document nothing below the root is tagged at all.

Values are normalized to BCP 47 (fr-frfr-FR); anything that is not a well-formed tag is dropped rather than emitted, since a wrong tag is worse for a screen reader than none.

What you are responsible for

The editor owns the accessibility of its own chrome. Anything above it belongs to the embedding page:

  • Landmarks. The editor exposes role="main" for the canvas and labelled regions for the ribbon, the status bar and its panels, but the surrounding page needs its own header, nav and footer landmarks. Do not wrap the editor in your own <main>: a page should have only one. For the same reason, avoid mounting two full editors on one page; each renders its own role="main".
  • Skip links. If the editor sits below a long masthead, provide a “skip to editor” link. The editor does not inject one.
  • Page title and language. Set <html lang> and a meaningful document title.
  • Custom toolbar items. Controls you add through uiExtensions or a custom toolbar tab join the ribbon’s arrow navigation automatically, but you must give them an accessible name (aria-label or visible text). Use data-toolbar-skip on a wrapper if a control manages its own focus and should stay out of the rail.
  • Custom node views. Interactive node views are your own to make operable; the editor does not manage focus inside them.

Conformance

The target is WCAG 2.1 Level AA. Automated checks (axe-core) run against the editor at rest and with each toolbar tab, panel and dialog open; keyboard behaviour is covered by end-to-end tests in a real browser. Automated tooling catches roughly a third of accessibility issues, so these are a floor, not a guarantee.

Known gaps:

  • The bullet-list button never reports aria-pressed="true", even inside a bulleted list.
  • The slash-menu listbox and the link form’s error message use fixed DOM ids, so two editors on one page produce duplicate ids and broken aria-controls / aria-describedby references.
Last updated on