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.
Live Playground
Section titled “Live Playground”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.
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);}import { Component, signal } from '@angular/core';import { DomternalEditorComponent } from '@domternal/angular';import { Editor, Document, Paragraph, Text, Focus } from '@domternal/core';
@Component({ selector: 'app-editor', imports: [DomternalEditorComponent], templateUrl: './editor.html', styles: [` :host ::ng-deep .has-focus { border-left: 3px solid #2563eb; background: rgba(37, 99, 235, 0.04); } `],})export class EditorComponent { editor = signal<Editor | null>(null); extensions = [Document, Paragraph, Text, Focus];}<domternal-editor [extensions]="extensions" (editorCreated)="editor.set($event)"/>import { Domternal } from '@domternal/react';import { Document, Paragraph, Text, Focus } from '@domternal/core';
export default function Editor() { return ( <Domternal extensions={[Document, Paragraph, Text, Focus]} > <Domternal.Content /> </Domternal> );}Style the focused block with CSS:
.has-focus { border-left: 3px solid #2563eb; background: rgba(37, 99, 235, 0.04);}import { Editor, Document, Paragraph, Text, Focus } from '@domternal/core';
const editor = new Editor({ element: document.getElementById('editor')!, extensions: [ Document, Paragraph, Text, Focus.configure({ className: 'focused-node', mode: 'deepest', }), ],});Focus is purely visual (CSS class decoration). Add your own styles for the configured class name.
Options
Section titled “Options”| Option | Type | Default | Description |
|---|---|---|---|
className | string | 'has-focus' | CSS class added to focused nodes |
mode | 'all' | 'deepest' | 'shallowest' | 'all' | Which nodes in the selection path to mark |
| Mode | Behavior | Example |
|---|---|---|
all | All nodes containing the selection | Blockquote + paragraph + list item all get the class |
deepest | Only the innermost focused node | Just the paragraph inside the blockquote |
shallowest | Only the outermost focused node | Just the blockquote |
Focus.configure({ className: 'active-block', mode: 'deepest', // only the innermost node})Commands
Section titled “Commands”Focus does not register any commands.
Keyboard shortcuts
Section titled “Keyboard shortcuts”Focus does not register any keyboard shortcuts.
Input rules
Section titled “Input rules”Focus does not register any input rules.
Toolbar items
Section titled “Toolbar items”Focus does not register any toolbar items.
How it works
Section titled “How it works”ProseMirror decorations
Section titled “ProseMirror decorations”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.):
- Collect: Walks all nodes between
selection.fromandselection.tousingdoc.nodesBetween(), filtering out text nodes - Filter: Applies the
modesetting to select which nodes get decorated - 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.
Node selection logic
Section titled “Node selection logic”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
No blur handling
Section titled “No blur handling”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.
Exports
Section titled “Exports”import { Focus, focusPluginKey } from '@domternal/core';import type { FocusOptions } from '@domternal/core';| Export | Type | Description |
|---|---|---|
Focus | Extension | The focus extension |
focusPluginKey | PluginKey | The ProseMirror plugin key |
FocusOptions | TypeScript type | Options for Focus.configure() |
Source
Section titled “Source”@domternal/core - Focus.ts