Skip to content

Focus

Focus adds a CSS class to nodes that contain the current selection. This lets you style the focused paragraph, heading, or block with custom CSS. You can choose to highlight all nodes in the selection path, only the deepest (innermost), or only the shallowest (outermost).

Not included in StarterKit. Add it separately.

Click in different paragraphs to see the focus highlight. Use the mode buttons to switch between all, deepest, and shallowest. Try clicking inside the blockquote with all mode to see both the blockquote and its inner paragraph highlighted.

Click to try it out
import { Editor, Document, Paragraph, Text, Focus } from '@domternal/core';
import '@domternal/theme';
const editor = new Editor({
element: document.getElementById('editor')!,
extensions: [Document, Paragraph, Text, Focus],
});

Add CSS to style the focused node:

.has-focus {
border-left: 3px solid #2563eb;
background: rgba(37, 99, 235, 0.04);
}
OptionTypeDefaultDescription
classNamestring'has-focus'CSS class added to focused nodes
mode'all' | 'deepest' | 'shallowest''all'Which nodes in the selection path to mark
ModeBehaviorExample
allAll nodes containing the selectionBlockquote + paragraph + list item all get the class
deepestOnly the innermost focused nodeJust the paragraph inside the blockquote
shallowestOnly the outermost focused nodeJust the blockquote
Focus.configure({
className: 'active-block',
mode: 'deepest', // only the innermost node
})

Focus does not register any commands.

Focus does not register any keyboard shortcuts.

Focus does not register any input rules.

Focus does not register any toolbar items.

Focus creates a ProseMirror plugin that applies Decoration.node decorations to focused nodes. The plugin uses the decorations prop, which runs on every state update (selection change, transaction, etc.):

  1. Collect: Walks all nodes between selection.from and selection.to using doc.nodesBetween(), filtering out text nodes
  2. Filter: Applies the mode setting to select which nodes get decorated
  3. Decorate: Creates Decoration.node(pos, pos + nodeSize, { class: className }) for each selected node

The decorations add the CSS class to the DOM element without modifying the document. When the selection changes, the old decorations are replaced with new ones.

A node is considered “focused” if it contains any part of the selection range. The check is: pos <= to && nodeEnd >= from. This works for:

  • Collapsed cursor: The paragraph (and its parents) containing the cursor
  • Text selection: All blocks that overlap with the selection
  • Node selection: The selected node itself

Focus only adds decorations when the editor has the selection. The decorations are computed from state.selection on every state update. When the editor loses focus, ProseMirror’s selection state is preserved, so the decorations remain until the selection changes.

import { Focus, focusPluginKey } from '@domternal/core';
import type { FocusOptions } from '@domternal/core';
ExportTypeDescription
FocusExtensionThe focus extension
focusPluginKeyPluginKeyThe ProseMirror plugin key
FocusOptionsTypeScript typeOptions for Focus.configure()

@domternal/core - Focus.ts