The Future of Web Dev
The Future of Web Dev
Customizable Rich Text Editor with shadcn/ui & Lexical – shadcn-editor
Add a fully customizable rich text editor to your React app with shadcn-editor. Built with Lexical and shadcn/ui for maximum control.

shadcn-editor (editor-x) is a React rich text editor built with Lexical and shadcn/ui.
It supports formatted text, draggable blocks, slash commands, tables, images, equations, media embeds, and editable local components.
The toolbar, block picker, localization, and editor state can be customized for writing and content-management applications.
Features
- Headings, paragraphs, quotes, ordered lists, bullet lists, checklists, indentation, and text alignment.
- Bold, italic, underline, strikethrough, inline code, highlighting, text and background colors, font family, font size, subscript, superscript, and text case controls.
- Formatting toolbar, undo/redo history, contextual menus, floating text controls, link editing, and a command-driven block picker.
- Draggable blocks, multi-column layouts, collapsible sections, cards, pull quotes, reviews, and polls.
- Tables with cell-selection and hover actions, syntax-oriented code blocks, and mathematical equations.
- Image insertion, paste/drop handling, emoji, mentions, autocomplete, hashtags, and special text.
- YouTube, X/Twitter, and Figma embeds with automatic embed detection.
- Markdown typing shortcuts, Markdown/HTML/JSON-related import and export utilities, and customizable Markdown transformers.
- Find and replace, word and character counts, read-only switching, speech-to-text controls, and keyboard shortcuts.
- English, Arabic, and Hebrew labels with left-to-right and right-to-left editing layouts.
- Locally editable Lexical extensions, nodes, React toolbar components, Tailwind classes, and editor theme files.
- Additional repository modules for AI, chat, comments, and other specialized interactions that require their own integration.
Use Cases
- CMS article authoring with headings, links, tables, media, and serialized drafts.
- Knowledge-base and technical documentation editors with code blocks, equations, and structured writing.
- Internal document tools with slash commands, rich blocks, multilingual text, and review content.
- Learning-content editors with embedded media, checklists, and reusable block layouts.
Installation
Run the shadcn CLI in an existing React project. If components.json is missing, initialize shadcn/ui first:
npx shadcn@latest initRegister the @shadcn-editor namespace in the existing components.json. Keep your other configuration fields:
{
"registries": {
"@shadcn-editor": "https://shadcn-editor.vercel.app/r/{name}.json"
}
}Install the complete Editor X registry item:
npx shadcn@latest add @shadcn-editor/editor-xHow To Use Editor X
Create components/editor/editor-x.tsx after installing the registry item. This React component assembles the main Editor X toolbars, extensions, content blocks, slash menu, and activity controls.
It also exposes initialState and onChange for application-level persistence.
"use client";
import { useEffect, useMemo, useRef } from "react";
import {
$getRoot, defineExtension,
type SerializedEditorState,
} from "lexical";
import { ClipboardDOMImportExtension } from "@lexical/clipboard";
import {
ClearEditorExtension, HorizontalRuleExtension,
TabIndentationExtension,
} from "@lexical/extension";
import { HashtagExtension } from "@lexical/hashtag";
import { HistoryExtension } from "@lexical/history";
import { CheckListExtension, ListExtension } from "@lexical/list";
import {
CHECK_LIST, ELEMENT_TRANSFORMERS,
MULTILINE_ELEMENT_TRANSFORMERS, TEXT_FORMAT_TRANSFORMERS,
TEXT_MATCH_TRANSFORMERS, registerMarkdownShortcuts,
type Transformer,
} from "@lexical/markdown";
import { LexicalExtensionComposer } from "@lexical/react/LexicalExtensionComposer";
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
import { RichTextExtension } from "@lexical/rich-text";
import { TableExtension } from "@lexical/table";
import { AutoLinkExtension } from "@/components/editor/extensions/auto-link";
import { AutocompleteExtension } from "@/components/editor/extensions/autocomplete";
import { CardExtension } from "@/components/editor/extensions/card";
import { CodeExtension } from "@/components/editor/extensions/code";
import { CollapsibleExtension } from "@/components/editor/extensions/collapsible";
import { DateTimeExtension } from "@/components/editor/extensions/datetime";
import { DragDropPasteExtension } from "@/components/editor/extensions/drag-drop-paste";
import { EmojiExtension } from "@/components/editor/extensions/emoji";
import { EquationExtension } from "@/components/editor/extensions/equation";
import { FigmaExtension } from "@/components/editor/extensions/figma";
import { FormatStateExtension } from "@/components/editor/extensions/format-state";
import { ImageExtension } from "@/components/editor/extensions/image";
import { LayoutExtension } from "@/components/editor/extensions/layout";
import { LinkExtension } from "@/components/editor/extensions/link";
import { MentionExtension } from "@/components/editor/extensions/mention";
import { PollExtension } from "@/components/editor/extensions/poll";
import { PullQuoteExtension } from "@/components/editor/extensions/pullquote";
import { RubyExtension } from "@/components/editor/extensions/ruby";
import { ShortcutsExtension } from "@/components/editor/extensions/shortcuts";
import { SpecialTextExtension } from "@/components/editor/extensions/special-text";
import { SpeechToTextExtension } from "@/components/editor/extensions/speech-to-text";
import { TabFocusExtension } from "@/components/editor/extensions/tab-focus";
import { TwitterExtension } from "@/components/editor/extensions/twitter";
import { YouTubeExtension } from "@/components/editor/extensions/youtube";
import { ActivityBar } from "@/components/editor/plugins/activitybar/activitybar-plugin";
import { CountPlugin } from "@/components/editor/plugins/activitybar/count-plugin";
import { ReadOnlyTogglePlugin } from "@/components/editor/plugins/activitybar/read-only-toggle-plugin";
import { ShortcutPlugin } from "@/components/editor/plugins/activitybar/shortcut-plugin";
import { SpeechToTextPlugin } from "@/components/editor/plugins/activitybar/speech-to-text-plugin";
import { AutoEmbedPlugin } from "@/components/editor/plugins/auto-embed-plugin";
import { BlockInsert } from "@/components/editor/plugins/block-insert/block-insert-plugin";
import { InsertCodeBlockPlugin } from "@/components/editor/plugins/block-insert/insert-code-block-plugin";
import { InsertColumnsPlugin } from "@/components/editor/plugins/block-insert/insert-columns-plugin";
import { InsertEmojiPlugin } from "@/components/editor/plugins/block-insert/insert-emoji-plugin";
import { InsertEquationPlugin } from "@/components/editor/plugins/block-insert/insert-equation-plugin";
import { InsertHorizontalRulePlugin } from "@/components/editor/plugins/block-insert/insert-horizontal-rule-plugin";
import { InsertImagePlugin } from "@/components/editor/plugins/block-insert/insert-image-plugin";
import { InsertTablePlugin } from "@/components/editor/plugins/block-insert/insert-table-plugin";
import { ComponentPicker } from "@/components/editor/plugins/component-picker/component-picker-plugin";
import { ParagraphPickerPlugin } from "@/components/editor/plugins/component-picker/paragraph-picker-plugin";
import { HeadingPickerPlugin } from "@/components/editor/plugins/component-picker/heading-picker-plugin";
import { TablePickerPlugin } from "@/components/editor/plugins/component-picker/table-picker-plugin";
import { NumberedListPickerPlugin } from "@/components/editor/plugins/component-picker/numbered-list-picker-plugin";
import { BulletedListPickerPlugin } from "@/components/editor/plugins/component-picker/bulleted-list-picker-plugin";
import { CheckListPickerPlugin } from "@/components/editor/plugins/component-picker/check-list-picker-plugin";
import { QuotePickerPlugin } from "@/components/editor/plugins/component-picker/quote-picker-plugin";
import { CodePickerPlugin } from "@/components/editor/plugins/component-picker/code-picker-plugin";
import { DividerPickerPlugin } from "@/components/editor/plugins/component-picker/divider-picker-plugin";
import { ColumnsPickerPlugin } from "@/components/editor/plugins/component-picker/columns-picker-plugin";
import { ImagePickerPlugin } from "@/components/editor/plugins/component-picker/image-picker-plugin";
import { CardPickerPlugin } from "@/components/editor/plugins/component-picker/card-picker-plugin";
import { CollapsiblePickerPlugin } from "@/components/editor/plugins/component-picker/collapsible-picker-plugin";
import { DateTimePickerPlugin } from "@/components/editor/plugins/component-picker/datetime-picker-plugin";
import { PullQuotePickerPlugin } from "@/components/editor/plugins/component-picker/pullquote-picker-plugin";
import { ReviewPickerPlugin } from "@/components/editor/plugins/component-picker/review-picker-plugin";
import { PollPickerPlugin } from "@/components/editor/plugins/component-picker/poll-picker-plugin";
import { ContentEditable } from "@/components/editor/plugins/content-editable";
import { ContextMenuPlugin } from "@/components/editor/plugins/context-menu-plugin";
import { ReactFindReplaceExtension } from "@/components/editor/plugins/decorator/find-replace-panel";
import { ReactReviewExtension } from "@/components/editor/plugins/decorator/review-plugin";
import { DraggableBlockPlugin } from "@/components/editor/plugins/draggable-block-plugin";
import { EmojiPickerPlugin } from "@/components/editor/plugins/emoji-picker-plugin";
import { FloatingToolbarPlugin } from "@/components/editor/plugins/floating/floating-toolbar-plugin";
import { LinkEditorPlugin } from "@/components/editor/plugins/floating/link-editor-plugin";
import { LinkToolbarPlugin } from "@/components/editor/plugins/toolbar/link-toolbar-plugin";
import { RubyEditorPlugin } from "@/components/editor/plugins/floating/ruby-editor-plugin";
import { RubyToolbarPlugin } from "@/components/editor/plugins/toolbar/ruby-toolbar-plugin";
import { TableHoverActionsPlugin } from "@/components/editor/plugins/floating/table-hover-actions-plugin";
import { LanguageProvider, LanguageSelectorPlugin, useLanguage } from "@/components/editor/plugins/i18n-plugin";
import { MentionPlugin } from "@/components/editor/plugins/mention-plugin";
import { BlockFormatToolbarPlugin } from "@/components/editor/plugins/toolbar/block-format-toolbar-plugin";
import { ClearToolbarPlugin } from "@/components/editor/plugins/toolbar/clear-toolbar-plugin";
import { ColorToolbarPlugin } from "@/components/editor/plugins/toolbar/color-toolbar-plugin";
import { ElementFormatToolbarPlugin } from "@/components/editor/plugins/toolbar/element-format-toolbar-plugin";
import { FontFamilyToolbarPlugin } from "@/components/editor/plugins/toolbar/font-family-toolbar-plugin";
import { FontSizeToolbarPlugin } from "@/components/editor/plugins/toolbar/font-size-toolbar-plugin";
import { HistoryToolbarPlugin } from "@/components/editor/plugins/toolbar/history-toolbar-plugin";
import { ImportExportToolbarPlugin } from "@/components/editor/plugins/toolbar/import-export-toolbar-plugin";
import { IndentToolbarPlugin } from "@/components/editor/plugins/toolbar/indent-toolbar-plugin";
import { TextFormatToolbarPlugin } from "@/components/editor/plugins/toolbar/text-format-toolbar-plugin";
import { Toolbar } from "@/components/editor/plugins/toolbar/toolbar-plugin";
import { editorTheme } from "@/components/editor/theme";
import { EMOJI } from "@/components/editor/transformers/emoji-transformer";
import { HR } from "@/components/editor/transformers/horizontal-rule-transformer";
import { IMAGE } from "@/components/editor/transformers/image-transformer";
import { TABLE } from "@/components/editor/transformers/table-transformer";
const transformers: Transformer[] = [
TABLE, HR, IMAGE, EMOJI, CHECK_LIST,
...ELEMENT_TRANSFORMERS,
...MULTILINE_ELEMENT_TRANSFORMERS,
...TEXT_FORMAT_TRANSFORMERS,
...TEXT_MATCH_TRANSFORMERS,
];
function StateBridge({
initialState, onChange,
}: {
initialState?: SerializedEditorState;
onChange?: (state: SerializedEditorState) => void;
}) {
const [editor] = useLexicalComposerContext();
const initial = useRef(initialState);
const callback = useRef(onChange);
callback.current = onChange;
useEffect(() => {
if (initial.current) {
editor.setEditorState(editor.parseEditorState(initial.current));
}
return editor.registerUpdateListener(({ editorState }) => {
callback.current?.(editorState.toJSON());
});
}, [editor]);
return null;
}
function EditorFrame({ children }: { children: React.ReactNode }) {
const { language, dir } = useLanguage();
return <div dir={dir} lang={language} className="flex min-h-96 flex-col overflow-hidden rounded-lg border">{children}</div>;
}
export function EditorX({
initialState, onChange,
}: {
initialState?: SerializedEditorState;
onChange?: (state: SerializedEditorState) => void;
}) {
const extension = useMemo(() => defineExtension({
name: "my-app/EditorX",
namespace: "my-app-editor",
theme: editorTheme,
dependencies: [
RichTextExtension, HistoryExtension, TabIndentationExtension,
ListExtension, CheckListExtension, HashtagExtension,
LinkExtension, AutoLinkExtension, CodeExtension,
LayoutExtension, EmojiExtension, EquationExtension,
TableExtension, HorizontalRuleExtension, ImageExtension,
MentionExtension, SpecialTextExtension, AutocompleteExtension,
DragDropPasteExtension, TabFocusExtension,
SpeechToTextExtension, ShortcutsExtension, CardExtension,
CollapsibleExtension, DateTimeExtension, PullQuoteExtension,
ReactReviewExtension, PollExtension, RubyExtension,
YouTubeExtension, TwitterExtension, FigmaExtension,
FormatStateExtension, ReactFindReplaceExtension,
ClearEditorExtension, ClipboardDOMImportExtension,
],
register: (editor) => registerMarkdownShortcuts(editor, transformers),
}), []);
return (
<LanguageProvider defaultLanguage="en">
<LexicalExtensionComposer extension={extension} contentEditable={null}>
<EditorFrame>
<Toolbar>
<HistoryToolbarPlugin />
<BlockFormatToolbarPlugin />
<FontFamilyToolbarPlugin />
<FontSizeToolbarPlugin />
<ColorToolbarPlugin />
<TextFormatToolbarPlugin formats="all" />
<ElementFormatToolbarPlugin formats="all" />
<IndentToolbarPlugin />
<BlockInsert>
<InsertCodeBlockPlugin />
<InsertColumnsPlugin />
<InsertEmojiPlugin />
<InsertEquationPlugin />
<InsertHorizontalRulePlugin />
<InsertImagePlugin />
<InsertTablePlugin />
</BlockInsert>
<ClearToolbarPlugin />
<ImportExportToolbarPlugin transformers={transformers} />
</Toolbar>
<div className="relative min-h-64 flex-1">
<ContentEditable variant="draggable" />
<DraggableBlockPlugin />
<FloatingToolbarPlugin>
<LinkToolbarPlugin />
<RubyToolbarPlugin />
</FloatingToolbarPlugin>
<LinkEditorPlugin />
<RubyEditorPlugin />
<TableHoverActionsPlugin />
<EmojiPickerPlugin />
<MentionPlugin />
<AutoEmbedPlugin />
<ContextMenuPlugin />
<ComponentPicker>
<ParagraphPickerPlugin />
<HeadingPickerPlugin />
<TablePickerPlugin />
<NumberedListPickerPlugin />
<BulletedListPickerPlugin />
<CheckListPickerPlugin />
<QuotePickerPlugin />
<CodePickerPlugin />
<DividerPickerPlugin />
<ColumnsPickerPlugin />
<ImagePickerPlugin />
<CardPickerPlugin />
<CollapsiblePickerPlugin />
<DateTimePickerPlugin />
<PullQuotePickerPlugin />
<ReviewPickerPlugin />
<PollPickerPlugin />
</ComponentPicker>
</div>
<ActivityBar>
<CountPlugin />
<SpeechToTextPlugin />
<ReadOnlyTogglePlugin />
<ShortcutPlugin />
<LanguageSelectorPlugin />
</ActivityBar>
<StateBridge initialState={initialState} onChange={onChange} />
</EditorFrame>
</LexicalExtensionComposer>
</LanguageProvider>
);
}
Render the editor from a Next.js client component and store changes as Lexical JSON. The example below persists drafts in the browser:
"use client";
import { useEffect, useState } from "react";
import type { SerializedEditorState } from "lexical";
import { EditorX } from "@/components/editor/editor-x";
const storageKey = "article-draft";
export default function EditorPage() {
const [initialState, setInitialState] = useState<SerializedEditorState>();
const [loaded, setLoaded] = useState(false);
useEffect(() => {
const saved = localStorage.getItem(storageKey);
if (saved) {
try { setInitialState(JSON.parse(saved)); }
catch { localStorage.removeItem(storageKey); }
}
setLoaded(true);
}, []);
if (!loaded) return null;
return (
<EditorX
initialState={initialState}
onChange={(state) => localStorage.setItem(storageKey, JSON.stringify(state))}
/>
);
}For server-backed drafts, send the serialized state from
onChangeto your own authenticated endpoint. Validate and sanitize rendered content before displaying it to other users. The editor does not create a storage service or file-upload backend.
Editor Architecture and Configuration
The editor uses defineExtension() for Lexical behavior and React plugins for visible controls. Register an extension in dependencies before rendering controls that depend on its nodes or commands. The LexicalExtensionComposer makes the editor instance available to the toolbar and plugins.
| Configuration/API | Function |
|---|---|
defineExtension({ name, namespace, dependencies, theme, register }) | Creates the editor configuration and registers optional behaviors. |
dependencies | Array of Lexical and shadcn-editor extension objects. |
theme: editorTheme | Lexical node-to-CSS-class mapping defined in components/editor/theme.ts. |
register(editor) | Optional extension registration callback. Example: Markdown shortcut transformers. |
LexicalExtensionComposer | Provides the editor context to descendants. |
useLexicalComposerContext() | Returns the editor instance in a component rendered under the composer. |
editor.getEditorState().toJSON() | Serializes document state. |
editor.parseEditorState(json) | Parses a saved Lexical state object or JSON string. |
editor.setEditorState(state) | Restores parsed state. |
editor.registerUpdateListener(callback) | Subscribes to editor state updates. Return the unregister function from an effect. |
editor.setEditable(boolean) | Changes editability programmatically. |
Editor X Props and Component Options
| Component / Prop | Type, Default, and Behavior |
|---|---|
EditorX.initialState | SerializedEditorState?. Optional initial JSON state in the article’s wrapper. |
EditorX.onChange | (state: SerializedEditorState) => void. Optional callback in the article’s wrapper. |
ContentEditable.variant | "default" | "toolbar" | "draggable", default "default". Changes padding for the editable region. |
ContentEditable.className | string?. Extra classes on the editable element. |
ContentEditable.placeholder | Partial<Record<"en" | "ar" | "he", string>>?. Localized placeholder strings. |
ContentEditable.placeholderClassName | string?. Classes for placeholder appearance. |
LanguageProvider.defaultLanguage | "en" | "ar" | "he", default "en". |
LanguageSelector | value, onValueChange, optional languages and className. |
TextFormatToolbarPlugin.formats | "basic" | "all", default "all". |
ElementFormatToolbarPlugin.formats | "basic" | "all", default "all". |
ImportExportToolbarPlugin.transformers | Transformer[]?. Defaults to Lexical’s TRANSFORMERS. |
CountPlugin.maxLength | number?. Displays length information and mounts the Lexical character-limit plugin when set. |
Toolbar | Accepts React div props, including children and className. |
ActivityBar | Requires children; accepts optional className. |
ComponentPicker.children | React.ReactNode. Picker entries register themselves under this parent. |
BlockInsert | Uses shadcn ButtonGroup component props. |
Available Extensions and Plugins
Editor X uses extensions to enable document node types and editing commands. The toolbar and other React plugins make those commands accessible in the UI. The registry also contains optional modules beyond the default editor composition.
Text, Formatting, and History
| Extension / Plugin | Function |
|---|---|
RichTextExtension | Paragraphs, headings, quotes, and standard rich-text editing. |
HistoryExtension / HistoryToolbarPlugin | Undo and redo. |
ListExtension / CheckListExtension | Ordered, bullet, and checklist nodes. |
BlockFormatToolbarPlugin | Switch between paragraph, headings, quote, and list blocks. |
TextFormatToolbarPlugin | Bold, italic, underline, strike, code, text case, subscript, superscript, and highlights. |
FontFamilyToolbarPlugin / FontSizeToolbarPlugin | Font family and size controls. |
ColorToolbarPlugin | Text and highlight/background color. |
ElementFormatToolbarPlugin / IndentToolbarPlugin | Alignment and indentation. |
ClearToolbarPlugin / ClearEditorExtension | Clear formatting and editor content controls. |
FormatStateExtension | Tracks formatting values used by toolbar controls. |
ShortcutsExtension / ShortcutPlugin | Keyboard editing shortcuts and in-editor shortcut reference. |
TabFocusExtension / TabIndentationExtension | Keyboard tab navigation and indentation behavior. |
Block Editing, Media, and Structured Content
| Extension / Plugin | Function |
|---|---|
CodeExtension / InsertCodeBlockPlugin | Code blocks and code insertion. |
ImageExtension / InsertImagePlugin | Image node insertion. |
DragDropPasteExtension | Handles image-file paste/drop and inserts image nodes from read files. |
TableExtension / InsertTablePlugin | Tables and table insertion. |
TableHoverActionsPlugin | Contextual table editing controls. |
EquationExtension / InsertEquationPlugin | Mathematical equation nodes and insertion. |
LayoutExtension / InsertColumnsPlugin | Multi-column document layouts. |
HorizontalRuleExtension / InsertHorizontalRulePlugin | Horizontal dividers. |
CardExtension / CardPickerPlugin | Card-style content blocks. |
CollapsibleExtension / CollapsiblePickerPlugin | Expandable document sections. |
DateTimeExtension / DateTimePickerPlugin | Date/time blocks. |
PullQuoteExtension / PullQuotePickerPlugin | Pull quotes. |
PollExtension / PollPickerPlugin | Poll blocks. |
ReactReviewExtension / ReviewPickerPlugin | Review/annotation-style blocks. |
RubyExtension / RubyEditorPlugin | Ruby annotation text. |
YouTubeExtension, TwitterExtension, FigmaExtension | Embeddable external content nodes. |
AutoEmbedPlugin | Recognizes eligible embed URLs and presents an insertion action. |
Navigation, Insertion, and Editing Tools
| Extension / Plugin | Function |
|---|---|
ComponentPicker | Slash-triggered block insertion menu. |
ParagraphPickerPlugin, HeadingPickerPlugin | Paragraph and heading options inside slash menu. |
BulletedListPickerPlugin, NumberedListPickerPlugin, CheckListPickerPlugin | List insertion choices. |
QuotePickerPlugin, CodePickerPlugin, DividerPickerPlugin | Quotes, code, and dividers. |
TablePickerPlugin, ColumnsPickerPlugin, ImagePickerPlugin | Structured content insertion. |
CardPickerPlugin, CollapsiblePickerPlugin, PollPickerPlugin | Specialized content blocks. |
DraggableBlockPlugin | Block drag handles for reordering document sections. |
FloatingToolbarPlugin | Floating formatting controls near selected content. |
LinkExtension, AutoLinkExtension, LinkEditorPlugin | Link nodes, URL detection, and link editing controls. |
ContextMenuPlugin | Editing context menu. |
EmojiExtension, EmojiPickerPlugin | Emoji nodes and emoji insertion picker. |
MentionExtension, MentionPlugin | Mention nodes and mention insertion. |
AutocompleteExtension | Autocomplete editing behavior. |
HashtagExtension / SpecialTextExtension | Hashtag and specially formatted inline text. |
ReactFindReplaceExtension | Find and replace UI inside the editor. |
CountPlugin | Word and character counts. |
ReadOnlyTogglePlugin | Read-only/editable mode switch. |
SpeechToTextExtension / SpeechToTextPlugin | Speech recognition control, subject to browser permission/API availability. |
LanguageSelectorPlugin | Switches English, Arabic, and Hebrew labels and direction. |
ImportExportToolbarPlugin | File export/import controls, Markdown transformers, and HTML export. |
Customize Toolbar, Plugins, and Document Types
Add a feature by importing its extension, inserting it in dependencies, and rendering its React control where appropriate. For example, put a link button beside the text-formatting controls:
import { LinkToolbarPlugin } from "@/components/editor/plugins/toolbar/link-toolbar-plugin";
// Inside the existing <Toolbar> in EditorX:
<LinkToolbarPlugin />To limit the formatting toolbar to basic styles, use formats="basic". For more formatting controls, use formats="all". To display a character limit in the activity bar, set a numeric maxLength:
<TextFormatToolbarPlugin formats="basic" />
<ElementFormatToolbarPlugin formats="basic" />
<CountPlugin maxLength={5000} />Edit components/editor/theme.ts to change the classes assigned to headings, paragraphs, code, tables, links, and other Lexical nodes. The theme.css file contains editor-specific selectors. Change the source Tailwind classes directly when customizing toolbar spacing, block padding, and control appearance.
The ContentEditable component accepts a localized placeholder object:
<ContentEditable
variant="draggable"
placeholder={{ en: "Write a draft...", ar: "اكتب هنا...", he: "כתבו כאן..." }}
className="min-h-96"
/>Saving, Restoring, and Exporting Content
| Format / Operation | Implementation |
|---|---|
| Lexical JSON | Use editorState.toJSON() for persistence and editor.parseEditorState() to restore documents. |
| Markdown | Register registerMarkdownShortcuts with the selected transformers. Use @lexical/markdown conversion functions for Markdown output. |
| HTML | Use $generateHtmlFromNodes(editor) from @lexical/html inside an editor read or update context. |
| Text | Call $getRoot().getTextContent() inside editorState.read(). |
| File import/export | Render ImportExportToolbarPlugin and pass matching Markdown transformers when custom nodes require them. |
Example of extracting HTML and plain text from the editor:
import { $getRoot, type LexicalEditor } from "lexical";
import { $generateHtmlFromNodes } from "@lexical/html";
export function exportContent(editor: LexicalEditor) {
return editor.getEditorState().read(() => ({
html: $generateHtmlFromNodes(editor),
text: $getRoot().getTextContent(),
}));
}Keyboard Shortcuts
| Shortcut | Action |
|---|---|
Ctrl/Cmd + B / I / U | Bold / italic / underline. |
Ctrl/Cmd + Z | Undo. |
Ctrl + Y or Cmd + Shift + Z | Redo. |
Ctrl/Cmd + Alt + 1 / 2 / 3 | Heading 1 / 2 / 3. |
Ctrl/Cmd + Shift + 7 / 8 / 9 | Numbered / bulleted / checklist. |
Ctrl/Cmd + Alt + C | Code block formatting. |
Ctrl/Cmd + Shift + X | Strikethrough. |
Ctrl/Cmd + [ / ] | Outdent / indent. |
Ctrl/Cmd + \ | Clear formatting. |
Related Resources
- Editorcn: React Rich Text Editors for shadcn/ui
- Enhanced Tiptap Editor with Shadcn/UI – Shadcn Tiptap
- Block-Based Rich Text Editor with TailwindCSS and shadcn/ui – Mina Rich Editor
- Lexical
FAQs
Does the install command create a ready-to-import Editor X component?
The registry installs the editor extensions, nodes, plugins, and styling. Define the EditorX composition in your app, as shown in the usage example.
Which Editor X APIs should I use to save a document?
Serialize Lexical state with toJSON() and restore it through parseEditorState() and setEditorState(). Your app decides where to store that data.
Do image insertion and file uploads require a backend?
The editor can insert image nodes and read pasted image files locally. Permanent uploads and hosted image URLs require your application’s upload flow.
Does it include live collaboration?
Editor X has editing and review-related modules, but the registry does not provide a managed multi-user synchronization service.





