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 init

Register 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-x

How 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 onChange to 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/APIFunction
defineExtension({ name, namespace, dependencies, theme, register })Creates the editor configuration and registers optional behaviors.
dependenciesArray of Lexical and shadcn-editor extension objects.
theme: editorThemeLexical node-to-CSS-class mapping defined in components/editor/theme.ts.
register(editor)Optional extension registration callback. Example: Markdown shortcut transformers.
LexicalExtensionComposerProvides 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 / PropType, Default, and Behavior
EditorX.initialStateSerializedEditorState?. 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.classNamestring?. Extra classes on the editable element.
ContentEditable.placeholderPartial<Record<"en" | "ar" | "he", string>>?. Localized placeholder strings.
ContentEditable.placeholderClassNamestring?. Classes for placeholder appearance.
LanguageProvider.defaultLanguage"en" | "ar" | "he", default "en".
LanguageSelectorvalue, onValueChange, optional languages and className.
TextFormatToolbarPlugin.formats"basic" | "all", default "all".
ElementFormatToolbarPlugin.formats"basic" | "all", default "all".
ImportExportToolbarPlugin.transformersTransformer[]?. Defaults to Lexical’s TRANSFORMERS.
CountPlugin.maxLengthnumber?. Displays length information and mounts the Lexical character-limit plugin when set.
ToolbarAccepts React div props, including children and className.
ActivityBarRequires children; accepts optional className.
ComponentPicker.childrenReact.ReactNode. Picker entries register themselves under this parent.
BlockInsertUses 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 / PluginFunction
RichTextExtensionParagraphs, headings, quotes, and standard rich-text editing.
HistoryExtension / HistoryToolbarPluginUndo and redo.
ListExtension / CheckListExtensionOrdered, bullet, and checklist nodes.
BlockFormatToolbarPluginSwitch between paragraph, headings, quote, and list blocks.
TextFormatToolbarPluginBold, italic, underline, strike, code, text case, subscript, superscript, and highlights.
FontFamilyToolbarPlugin / FontSizeToolbarPluginFont family and size controls.
ColorToolbarPluginText and highlight/background color.
ElementFormatToolbarPlugin / IndentToolbarPluginAlignment and indentation.
ClearToolbarPlugin / ClearEditorExtensionClear formatting and editor content controls.
FormatStateExtensionTracks formatting values used by toolbar controls.
ShortcutsExtension / ShortcutPluginKeyboard editing shortcuts and in-editor shortcut reference.
TabFocusExtension / TabIndentationExtensionKeyboard tab navigation and indentation behavior.

Block Editing, Media, and Structured Content

Extension / PluginFunction
CodeExtension / InsertCodeBlockPluginCode blocks and code insertion.
ImageExtension / InsertImagePluginImage node insertion.
DragDropPasteExtensionHandles image-file paste/drop and inserts image nodes from read files.
TableExtension / InsertTablePluginTables and table insertion.
TableHoverActionsPluginContextual table editing controls.
EquationExtension / InsertEquationPluginMathematical equation nodes and insertion.
LayoutExtension / InsertColumnsPluginMulti-column document layouts.
HorizontalRuleExtension / InsertHorizontalRulePluginHorizontal dividers.
CardExtension / CardPickerPluginCard-style content blocks.
CollapsibleExtension / CollapsiblePickerPluginExpandable document sections.
DateTimeExtension / DateTimePickerPluginDate/time blocks.
PullQuoteExtension / PullQuotePickerPluginPull quotes.
PollExtension / PollPickerPluginPoll blocks.
ReactReviewExtension / ReviewPickerPluginReview/annotation-style blocks.
RubyExtension / RubyEditorPluginRuby annotation text.
YouTubeExtension, TwitterExtension, FigmaExtensionEmbeddable external content nodes.
AutoEmbedPluginRecognizes eligible embed URLs and presents an insertion action.

Navigation, Insertion, and Editing Tools

Extension / PluginFunction
ComponentPickerSlash-triggered block insertion menu.
ParagraphPickerPlugin, HeadingPickerPluginParagraph and heading options inside slash menu.
BulletedListPickerPlugin, NumberedListPickerPlugin, CheckListPickerPluginList insertion choices.
QuotePickerPlugin, CodePickerPlugin, DividerPickerPluginQuotes, code, and dividers.
TablePickerPlugin, ColumnsPickerPlugin, ImagePickerPluginStructured content insertion.
CardPickerPlugin, CollapsiblePickerPlugin, PollPickerPluginSpecialized content blocks.
DraggableBlockPluginBlock drag handles for reordering document sections.
FloatingToolbarPluginFloating formatting controls near selected content.
LinkExtension, AutoLinkExtension, LinkEditorPluginLink nodes, URL detection, and link editing controls.
ContextMenuPluginEditing context menu.
EmojiExtension, EmojiPickerPluginEmoji nodes and emoji insertion picker.
MentionExtension, MentionPluginMention nodes and mention insertion.
AutocompleteExtensionAutocomplete editing behavior.
HashtagExtension / SpecialTextExtensionHashtag and specially formatted inline text.
ReactFindReplaceExtensionFind and replace UI inside the editor.
CountPluginWord and character counts.
ReadOnlyTogglePluginRead-only/editable mode switch.
SpeechToTextExtension / SpeechToTextPluginSpeech recognition control, subject to browser permission/API availability.
LanguageSelectorPluginSwitches English, Arabic, and Hebrew labels and direction.
ImportExportToolbarPluginFile 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 / OperationImplementation
Lexical JSONUse editorState.toJSON() for persistence and editor.parseEditorState() to restore documents.
MarkdownRegister registerMarkdownShortcuts with the selected transformers. Use @lexical/markdown conversion functions for Markdown output.
HTMLUse $generateHtmlFromNodes(editor) from @lexical/html inside an editor read or update context.
TextCall $getRoot().getTextContent() inside editorState.read().
File import/exportRender 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

ShortcutAction
Ctrl/Cmd + B / I / UBold / italic / underline.
Ctrl/Cmd + ZUndo.
Ctrl + Y or Cmd + Shift + ZRedo.
Ctrl/Cmd + Alt + 1 / 2 / 3Heading 1 / 2 / 3.
Ctrl/Cmd + Shift + 7 / 8 / 9Numbered / bulleted / checklist.
Ctrl/Cmd + Alt + CCode block formatting.
Ctrl/Cmd + Shift + XStrikethrough.
Ctrl/Cmd + [ / ]Outdent / indent.
Ctrl/Cmd + \Clear formatting.

Related Resources

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.

Talha Mujahid

Talha Mujahid

Software Engineer, Full Stack Developer, Clean Code Enthusiast

Leave a Reply

Your email address will not be published. Required fields are marked *