packages/lexical-website/docs/react/plugins.md
:::tip
Most of these plugins are available as modern Lexical Extensions, which do not require separate configuration and can have more efficient implementations. You should prefer using extensions over plugins where available, and migrate any project to extensions with LexicalExtensionComposer or LexicalExtensionEditorComposer if it is still using LexicalComposer.
:::
React-based plugins use a Lexical editor instance from <LexicalExtensionComposer> or <LexicalComposer> (legacy) context:
import {LexicalComposer} from '@lexical/react/LexicalComposer';
import {PlainTextPlugin} from '@lexical/react/LexicalPlainTextPlugin';
import {ContentEditable} from '@lexical/react/LexicalContentEditable';
import {HistoryPlugin} from '@lexical/react/LexicalHistoryPlugin';
import {OnChangePlugin} from '@lexical/react/LexicalOnChangePlugin';
const initialConfig = {
namespace: 'MyEditor',
theme,
onError,
};
<LexicalComposer initialConfig={initialConfig}>
<PlainTextPlugin
contentEditable={<ContentEditable />}
placeholder={<div>Enter some text...</div>}
/>
<HistoryPlugin />
<OnChangePlugin onChange={onChange} />
...
</LexicalComposer>;
Note: Many plugins might require you to register the one or many Lexical nodes in order for the plugin to work. You can do this by passing a reference to the node to the
nodesarray in your initial editor configuration.
const initialConfig = {
namespace: 'MyEditor',
theme,
nodes: [ListNode, ListItemNode], // Pass the references to the nodes here
onError,
};
LexicalPlainTextPluginReact wrapper for @lexical/plain-text that adds major features for plain text editing, including typing, deletion and copy/pasting.
<PlainTextPlugin
contentEditable={
<ContentEditable
aria-placeholder={'Enter some text...'}
placeholder={<div>Enter some text...</div>}
/>
}
ErrorBoundary={LexicalErrorBoundary}
/>
:::tip
Use PlainTextExtension when using extensions
:::
LexicalRichTextPluginReact wrapper for @lexical/rich-text that adds major features for rich text editing, including typing, deletion, copy/pasting, indent/outdent and bold/italic/underline/strikethrough text formatting.
<RichTextPlugin
contentEditable={
<ContentEditable
aria-placeholder={'Enter some text...'}
placeholder={<div>Enter some text...</div>}
/>
}
ErrorBoundary={LexicalErrorBoundary}
/>
:::tip
Use RichTextExtension when using extensions
:::
LexicalAutoFocusPluginAutomatically focuses the editor when the component mounts and optionally places the cursor at the start or end of the root content.
<AutoFocusPlugin defaultSelection="rootEnd" />
:::tip
Use AutoFocusExtension when using extensions
:::
LexicalOnChangePluginPlugin that calls onChange whenever Lexical state is updated. Using ignoreHistoryMergeTagChange (true by default) and ignoreSelectionChange (false by default) can give more granular control over changes that are causing onChange call.
<OnChangePlugin onChange={onChange} />
:::tip
When using extensions, subscribe to EditorStateExtension for a signal-based equivalent
:::
LexicalHistoryPluginReact wrapper for @lexical/history that adds support for history stack management and undo / redo commands.
<HistoryPlugin />
:::tip
Use HistoryExtension when using extensions
:::
LexicalLinkPluginReact wrapper for @lexical/link that adds support for links, including $toggleLink command support that toggles link for selected text.
<LinkPlugin />
:::tip
Use LinkExtension when using extensions
:::
LexicalClickableLinkPluginMakes LinkNode elements in the editor clickable, navigating to the link URL on click. Set disabled to true to turn off the behavior, or newTab to control whether links open in a new tab.
Note: Requires
LinkNodefrom@lexical/linkto be registered.
<ClickableLinkPlugin newTab={true} disabled={false} />
:::tip
Use ClickableLinkExtension when using extensions
:::
LexicalListPluginReact wrapper for @lexical/list that adds support for lists (ordered and unordered)
<ListPlugin />
:::tip
Use ListExtension when using extensions
:::
LexicalCheckListPluginReact wrapper for @lexical/list that adds support for check lists. Note that it requires some css to render check/uncheck marks. See PlaygroundEditorTheme.css for details.
<CheckListPlugin />
:::tip
Use CheckListExtension when using extensions
:::
LexicalTablePluginReact wrapper for @lexical/table that adds support for tables.
<TablePlugin />
:::tip
Use TableExtension when using extensions
:::
LexicalHorizontalRulePluginRegisters a command handler for INSERT_HORIZONTAL_RULE_COMMAND. When dispatched, it inserts a HorizontalRuleNode at the current selection.
Note: Requires
HorizontalRuleNodeto be registered. This plugin is deprecated in favor ofHorizontalRuleExtensionfrom@lexical/extension.
import {HorizontalRuleNode} from '@lexical/react/LexicalHorizontalRuleNode';
const config = {nodes: [HorizontalRuleNode]};
<HorizontalRulePlugin />
:::tip
Use HorizontalRuleExtension when using extensions
:::
LexicalTabIndentationPluginPlugin that allows tab indentation in combination with @lexical/rich-text.
<TabIndentationPlugin />
:::tip
Use TabIndentationExtension when using extensions
:::
LexicalHashtagPluginAutomatically detects hashtag patterns (e.g., #hello) in text as the user types and converts them into HashtagNode instances with distinct styling.
Note: Requires
HashtagNodefrom@lexical/hashtagto be registered.
import {HashtagNode} from '@lexical/hashtag';
const config = {nodes: [HashtagNode]};
<HashtagPlugin />
:::tip
Use HashtagExtension when using extensions
:::
LexicalAutoLinkPluginPlugin will convert text into links based on passed matchers list. In example below whenever user types url-like string it will automatically convert it into a link node
const URL_MATCHER =
/((https?:\/\/(www\.)?)|(www\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/;
const MATCHERS = [
(text) => {
const match = URL_MATCHER.exec(text);
if (match === null) {
return null;
}
const fullMatch = match[0];
return {
index: match.index,
length: fullMatch.length,
text: fullMatch,
url: fullMatch.startsWith('http') ? fullMatch : `https://${fullMatch}`,
// attributes: { rel: 'noreferrer', target: '_blank' }, // Optional link attributes
};
},
];
...
<AutoLinkPlugin matchers={MATCHERS} />
:::tip
Use AutoLinkExtension when using extensions
:::
LexicalAutoEmbedPluginWatches for pasted links that match any of the provided embed configurations (e.g., YouTube, Twitter URLs). When a match is found, it shows a menu offering to replace the link with an embedded node.
Note: Requires
LinkNodeandAutoLinkNodefrom@lexical/linkto be registered.
import {LexicalAutoEmbedPlugin, EmbedConfig, AutoEmbedOption} from '@lexical/react/LexicalAutoEmbedPlugin';
const YouTubeEmbedConfig = {
type: 'youtube',
parseUrl: (url) => {
const match = url.match(/youtube\.com\/watch\?v=([a-zA-Z0-9_-]+)/);
return match ? {url, id: match[1]} : null;
},
insertNode: (editor, result) => {
// Insert your custom embed node here
},
};
<LexicalAutoEmbedPlugin
embedConfigs={[YouTubeEmbedConfig]}
onOpenEmbedModalForConfig={(config) =>}
getMenuOptions={(config, embedFn, dismissFn) => [
new AutoEmbedOption('Embed', {onSelect: embedFn}),
new AutoEmbedOption('Dismiss', {onSelect: dismissFn}),
]}
/>
LexicalCharacterLimitPluginTracks the number of characters in the editor and renders a count of remaining characters. Uses OverflowNode to visually indicate when the limit is exceeded.
Note: Requires
OverflowNodefrom@lexical/overflowto be registered.
import {CharacterLimitPlugin} from '@lexical/react/LexicalCharacterLimitPlugin';
<CharacterLimitPlugin charset="UTF-16" maxLength={280} />
:::tip
Use OverflowExtension when using extensions
:::
LexicalClearEditorPluginAdds clearEditor command support to clear editor's content.
<ClearEditorPlugin />
:::tip
Use ClearEditorExtension when using extensions
:::
LexicalMarkdownShortcutPluginAdds markdown shortcut support: headings, lists, code blocks, quotes, links and inline styles (bold, italic, strikethrough).
<MarkdownShortcutPlugin />
LexicalTableOfContentsPluginThis plugin allows you to render a table of contents for a page from the headings from the editor. It listens to any deletions or modifications to those headings and updates the table of contents. Additionally, it's able to track any newly added headings and inserts them in the table of contents once they are created. This plugin also supports lazy loading - so you can defer adding the plugin until when the user needs it.
In order to use TableOfContentsPlugin, you need to pass a callback function in its children. This callback function gives you access to the up-to-date data of the table of contents. You can access this data through a single parameter for the callback which comes in the form of an array of arrays [[headingKey, headingTextContent, headingTag], [], [], ...]
headingKey: Unique key that identifies the heading.
headingTextContent: A string of the exact text of the heading.
headingTag: A string that reads either 'h1', 'h2', or 'h3'.
<TableOfContentsPlugin>
{(tableOfContentsArray) => {
return (
<MyCustomTableOfContentsPlugin tableOfContents={tableOfContentsArray} />
);
}}
</TableOfContentsPlugin>
LexicalEditorRefPluginAllows you to get a ref to the underlying editor instance outside of LexicalComposer, which is convenient when you want to interact with the editor from a separate part of your application.
const editorRef = useRef(null);
<EditorRefPlugin editorRef={editorRef} />;
LexicalCollaborationPluginIntegrates Yjs-based real-time collaborative editing into Lexical. Creates a Yjs binding between the editor state and a shared Yjs document, renders remote user cursors, and provides collaborative undo/redo history.
import {CollaborationPlugin} from '@lexical/react/LexicalCollaborationPlugin';
import {WebsocketProvider} from 'y-websocket';
import {Doc} from 'yjs';
function createProvider(id, yjsDocMap) {
const doc = new Doc();
yjsDocMap.set(id, doc);
return new WebsocketProvider('ws://localhost:1234', id, doc);
}
<CollaborationPlugin
id="my-document"
providerFactory={createProvider}
shouldBootstrap={true}
username="Alice"
cursorColor="#FF0000"
/>
LexicalDraggableBlockPluginAdds a draggable block handle that appears next to top-level block nodes when hovered. Users can drag and drop blocks to reorder them. A target line indicates the drop position.
Note: This plugin is experimental.
import {DraggableBlockPlugin_EXPERIMENTAL} from '@lexical/react/LexicalDraggableBlockPlugin';
const menuRef = useRef(null);
const targetLineRef = useRef(null);
<DraggableBlockPlugin_EXPERIMENTAL
anchorElem={document.body}
menuRef={menuRef}
targetLineRef={targetLineRef}
menuComponent={<div ref={menuRef} className="drag-handle">::</div>}
targetLineComponent={<div ref={targetLineRef} className="target-line" />}
isOnMenu={(el) => menuRef.current?.contains(el) ?? false}
/>
LexicalNodeEventPluginAttaches a DOM event listener on the editor root element that fires only when the event target corresponds to a node of the specified type.
import {NodeEventPlugin} from '@lexical/react/LexicalNodeEventPlugin';
<NodeEventPlugin
nodeType={ImageNode}
eventType="click"
eventListener={(event, editor, nodeKey) => {
console.log('Clicked on node:', nodeKey);
}}
/>
LexicalNodeMenuPluginRenders a menu anchored to a specific Lexical node identified by nodeKey. Provides keyboard navigation (arrow keys, enter, escape) through the menu options.
import {LexicalNodeMenuPlugin, MenuOption} from '@lexical/react/LexicalNodeMenuPlugin';
<LexicalNodeMenuPlugin
nodeKey={selectedNodeKey}
options={menuOptions}
onSelectOption={(option, textNode, closeMenu) => {
option.action();
closeMenu();
}}
menuRenderFn={(anchorRef, {options, selectedIndex, selectOptionAndCleanUp}) =>
anchorRef.current
? createPortal(
<ul>
{options.map((opt, i) => (
<li key={opt.key} onClick={() => selectOptionAndCleanUp(opt)}>
{opt.title}
</li>
))}
</ul>,
anchorRef.current,
)
: null
}
onClose={() => setSelectedNodeKey(null)}
/>
LexicalTypeaheadMenuPluginProvides a typeahead/autocomplete menu that appears when the user types a trigger character (e.g., @ for mentions, : for emoji). It listens to text changes, runs a trigger function against text before the cursor, and when a match is found, positions a menu at the trigger location.
import {
LexicalTypeaheadMenuPlugin,
useBasicTypeaheadTriggerMatch,
} from '@lexical/react/LexicalTypeaheadMenuPlugin';
const triggerMatch = useBasicTypeaheadTriggerMatch('@', {minLength: 1});
<LexicalTypeaheadMenuPlugin
triggerFn={triggerMatch}
options={mentionOptions}
onQueryChange={setQuery}
onSelectOption={(option, textNode, closeMenu) => {
editor.update(() => {
const mentionNode = $createMentionNode(option.name);
if (textNode) {
textNode.replace(mentionNode);
}
closeMenu();
});
}}
menuRenderFn={(anchorRef, {options, selectedIndex, selectOptionAndCleanUp}) =>
anchorRef.current
? createPortal(
<ul className="typeahead-menu">
{options.map((opt, i) => (
<li
key={opt.key}
className={i === selectedIndex ? 'selected' : ''}
onClick={() => selectOptionAndCleanUp(opt)}>
{opt.name}
</li>
))}
</ul>,
anchorRef.current,
)
: null
}
/>
LexicalSelectionAlwaysOnDisplayBy default, browser text selection becomes invisible when clicking away from the editor. This plugin ensures the selection remains visible.
<SelectionAlwaysOnDisplay />
:::tip
Use SelectionAlwaysOnDisplayExtension when using extensions
:::