Tiptap extension that allows you to add a class to the focused node with configurable focus modes
npx @tessl/cli install tessl/npm-tiptap--extension-focus@3.4.0The Focus extension allows you to add CSS classes to focused nodes in a Tiptap editor, providing visual focus indicators that enhance user experience and accessibility. It operates as a ProseMirror plugin that decorates nodes based on cursor position, offering three different focus modes for various use cases.
npm install @tiptap/extensionsimport { Focus, FocusOptions } from "@tiptap/extensions";For CommonJS:
const { Focus, FocusOptions } = require("@tiptap/extensions");Sub-path imports (also supported):
import { Focus, FocusOptions } from "@tiptap/extensions/focus";import { Editor } from "@tiptap/core";
import { Focus } from "@tiptap/extensions";
// Basic usage with default options
const editor = new Editor({
extensions: [
Focus,
// ... other extensions
],
content: "<p>Your content here</p>",
});
// Custom configuration
const editor = new Editor({
extensions: [
Focus.configure({
className: 'is-focused',
mode: 'deepest',
}),
// ... other extensions
],
content: "<p>Your content here</p>",
});The Focus extension integrates with Tiptap's extension system and uses ProseMirror's decoration API to efficiently add CSS classes to DOM elements without re-rendering content. Key components:
Creates a Tiptap extension that adds CSS classes to focused nodes based on cursor position.
import { Extension } from "@tiptap/core";
/**
* Focus extension that adds CSS classes to focused nodes
* Extends Tiptap's base Extension class with FocusOptions configuration
*/
const Focus: Extension<FocusOptions>;Configures the Focus extension with custom options.
/**
* Configure the Focus extension with custom options
* @param options - Configuration options for focus behavior
* @returns Configured Focus extension instance
*/
Focus.configure(options: Partial<FocusOptions>): Extension<FocusOptions>;Marks all nodes in the selection path as focused (default behavior).
// Configuration for 'all' mode
Focus.configure({
mode: 'all',
className: 'has-focus'
})Marks only the deepest (innermost) node in the selection as focused.
// Configuration for 'deepest' mode
Focus.configure({
mode: 'deepest',
className: 'has-focus'
})Marks only the shallowest (outermost) node in the selection as focused.
// Configuration for 'shallowest' mode
Focus.configure({
mode: 'shallowest',
className: 'has-focus'
})/**
* Configuration options for the Focus extension
*/
interface FocusOptions {
/**
* The CSS class name that should be added to the focused node
* @default 'has-focus'
* @example 'is-focused'
*/
className: string;
/**
* The mode by which the focused node is determined
* - 'all': All nodes in the selection path are marked as focused
* - 'deepest': Only the innermost node is marked as focused
* - 'shallowest': Only the outermost node is marked as focused
* @default 'all'
*/
mode: 'all' | 'deepest' | 'shallowest';
}import { Editor } from "@tiptap/core";
import { Focus } from "@tiptap/extensions";
const editor = new Editor({
extensions: [
Focus.configure({
className: 'editor-focused-node'
})
],
content: '<p>Your content here</p>',
});// Only highlight the innermost focused element
const editor = new Editor({
extensions: [
Focus.configure({
mode: 'deepest',
className: 'deeply-focused'
})
],
content: '<p>Your content here</p>',
});// Use ARIA-friendly class names for screen readers
const editor = new Editor({
extensions: [
Focus.configure({
className: 'aria-current-element',
mode: 'deepest'
})
],
content: '<p>Your content here</p>',
});/* Style focused nodes */
.has-focus {
outline: 2px solid #007acc;
outline-offset: 2px;
}
/* Different styles for different focus modes */
.deeply-focused {
background-color: rgba(0, 122, 204, 0.1);
border-radius: 4px;
}
.aria-current-element {
box-shadow: 0 0 0 2px #007acc;
}The Focus extension requires:
@tiptap/core for base Extension class@tiptap/pm/state for Plugin and PluginKey@tiptap/pm/view for Decoration and DecorationSet