A powerful, extensible, and modular rich text editor built with Lexical and React. react-lexify
is designed for developers who need a flexible editor that can be easily customized with plugins, collaborative features, and advanced content types.
⚠️ Important Note:
This editor is built on top of Lexical, which is an actively developed and evolving library maintained by Meta. As such, breaking changes or frequent updates may occur in Lexical’s core or plugin APIs.
We are committed to keepingreact-lexify
in sync with Lexical's latest stable versions and will try to publish updates as soon as possible to maintain compatibility and stability.
- Features
- Installation
- Basic Usage
- Editor Props
- Plugin Configuration
- Advanced Configuration
- Examples
- TypeScript Support
- Browser Support
- Contributing
- License
- 📝 Rich text and plain text editing
- 🔌 Plugin-based configuration (use only what you need)
- 🧩 Modular plugins for mentions, emojis, checklists, tables, and embeds
- 🔄 Real-time collaborative editing with Yjs + WebSocket
- 🧠 Slash command autocomplete, AI-style shortcuts, and dynamic inputs
- 🎨 Embedded drawing support via Excalidraw
- 📑 Table support with advanced features (resizing, merging, coloring)
- 🎥 Embed support for YouTube, Twitter, Figma, and more
- 🔧 Customizable editor theme and plugin behaviors
- 💬 Comment system support (optional)
- 💯 Written in TypeScript with full typings for all configs
npm install react-lexify
# or
yarn add react-lexify
# or
pnpm add react-lexify
Make sure you also have:
npm install react react-dom
⚠️ Important NoteTo ensure proper styling of the editor and toolbar, you must import the editor’s CSS file once globally.
🌸 Add the following import in your root file (e.g.,
index.tsx
,_app.tsx
, or layout):import "react-lexify/dist/react-lexify.css";✅ This is required for the editor to render styles correctly.
🎨 You can customize the look and feel later using the
theme
andtoolbarStyle
options.
import React from "react";
import Editor from "react-lexify";
export default function MyEditor() {
return (
<Editor
plugins={{
richText: true,
toolbar: true,
table: { enabled: true },
mentions: {
enabled: true,
renderMentionOption: (mention, selected) => (
<div style={{ background: selected ? "#eee" : "transparent" }}>
@{mention.meta?.name || mention.displayName}
</div>
),
},
emojis: true,
autoFocus: true,
}}
/>
);
}
To integrate react-lexify
into a Next.js App Router project safely (especially with plugins like excalidraw
that are not SSR-compatible), use the dynamic()
import to load the editor client-side only.
// components/editor/editor-shell.tsx
"use client";
import { Editor, EditorProps } from "react-lexify";
export default function EditorShell(props: EditorProps) {
return (
<div className="w-full max-w-full space-y-2 editor-shell">
<Editor {...props} />
</div>
);
}
// components/editor/editor-loader.tsx
import { Skeleton } from "@/components/ui/skeleton";
import { Loader2 } from "lucide-react";
export const EditorLoader = () => {
return (
<div className="relative w-full border rounded-md p-3 min-h-[300px] bg-white shadow-sm">
<div className="flex items-center gap-1 md:gap-2 lg:gap-3 mb-4 overflow-auto">
<Skeleton className="hidden lg:block h-6 w-6 rounded-full" />
<Skeleton className="hidden lg:block h-6 w-20" />
<Skeleton className="hidden lg:block h-6 w-16" />
<Skeleton className="h-6 w-6" />
<Skeleton className="h-6 w-6" />
<Skeleton className="h-6 w-6" />
<Skeleton className="h-6 w-6" />
<Skeleton className="hidden lg:block h-6 w-6" />
<Skeleton className="h-6 w-6" />
<Skeleton className="h-6 w-6" />
<Skeleton className="h-6 w-16" />
<Skeleton className="hidden lg:block h-6 w-16" />
<Skeleton className="hidden lg:block h-6 w-16" />
<Skeleton className="hidden lg:block h-6 w-16" />
<Skeleton className="hidden lg:block h-6 w-10 ml-auto" />
</div>
<Skeleton className="h-[280px] w-full rounded-md" />
<div className="absolute top-1/2 left-1/2 flex flex-col justify-center items-center -translate-x-1/2 -translate-y-1/2">
<Loader2 className="animate-spin text-muted-foreground" />
<p className="text-xs font-normal text-muted-foreground mt-2">
Loading Editor
</p>
</div>
</div>
);
};
// app/editor/page.tsx or your dynamic editor page
"use client";
import dynamic from "next/dynamic";
import { EditorLoader } from "@/components/editor/editor-loader";
// Dynamically import EditorShell to disable SSR
const EditorShell = dynamic(() => import("@/components/editor/editor-shell"), {
ssr: false,
loading: () => <EditorLoader />,
});
export default function EditorPage() {
return (
<div className="p-4">
<EditorShell
placeholder="Write your content here..."
plugins={{
richText: true,
toolbar: true,
history: true,
excalidraw: true,
}}
/>
</div>
);
}
✅ Why this setup?
Some plugins likeExcalidraw
orspeech-to-text
use browser-only APIs and will break on SSR. Usingdynamic()
withssr: false
ensures these are loaded only on the client.
The Editor
component accepts the following props:
interface EditorProps {
initialConfig?: Partial<LexicalComposerInitialConfig>;
plugins?: EditorPluginConfig;
placeholder?: string;
readOnly?: boolean;
/**
* @deprecated Use `plugins.mentions.fetchMentions` instead.
*/
fetchMentions?: (query: string) => Promise<Mention[]>;
/**
* @deprecated Use `plugins.mentions.onMentionSelect` instead.
*/
onMentionSelect?: (mention: Mention) => void;
/**
* @deprecated Use `plugins.mentions.renderMentionOption` instead.
*/
renderMentionOption?: (mention: Mention, isSelected: boolean) => JSX.Element;
onChange?: (output: EditorState | string) => void;
outputFormat?: "htmlString" | "editorState";
}
Prop | Type | Description |
---|---|---|
initialConfig |
Partial<LexicalComposerInitialConfig> |
Partial configuration for the Lexical composer. |
plugins |
EditorPluginConfig |
Configuration for all plugins. See Plugin Configuration. |
placeholder |
string |
Placeholder text for the editor when empty. |
readOnly |
boolean |
Makes the editor read-only when true . |
fetchMentions |
(query: string) => Promise<Mention[]> |
Deprecated – use plugins.mentions.fetchMentions instead. |
onMentionSelect |
(mention: Mention) => void |
Deprecated – use plugins.mentions.onMentionSelect instead. |
renderMentionOption |
(mention: Mention, isSelected: boolean) => JSX.Element |
Deprecated – use plugins.mentions.renderMentionOption instead. |
onChange |
(output: EditorState | string) => void |
Called whenever the editor content changes. Format is determined by outputFormat . |
outputFormat |
"htmlString" | "editorState" |
Determines the return format for onChange . Defaults to "htmlString" . |
The following props have been deprecated and will be removed in future versions:
Deprecated Prop | Use Instead |
---|---|
fetchMentions |
plugins.mentions.fetchMentions |
onMentionSelect |
plugins.mentions.onMentionSelect |
renderMentionOption |
plugins.mentions.renderMentionOption |
Reason: These props have been scoped under the plugins.mentions
object to improve modularity and plugin-specific configuration.
Action: Please migrate to the new structure under plugins.mentions
.
You can control what gets returned from the editor via the outputFormat
prop and handle the value through onChange
prop.
Prop | Type | Default | Description |
---|---|---|---|
onChange |
(output: EditorState | string) => void |
— | Called whenever the editor content changes. Depends on outputFormat . |
outputFormat |
"htmlString" | "editorState" |
"htmlString" |
Determines the output format passed to onChange . |
<Editor
outputFormat="htmlString"
onChange={(html) => {
console.log("HTML content:", html);
}}
/>
<Editor
outputFormat="editorState"
onChange={(editorState) => {
console.log("EditorState object:", editorState);
}}
/>
💡 Tip: Use
"htmlString"
when rendering or storing HTML; use"editorState"
when working with serialized Lexical states (e.g., for collaboration or advanced features).
The EditorPluginConfig
interface defines all available plugin options. Below is a detailed breakdown of each plugin category and available configuration.
-
autoFocus
:boolean
- Enables auto-focus on the editor when it mounts. Defaults tofalse
.<Editor plugins={{ autoFocus: true }} />
-
clearEditor
:boolean
- Adds a clear editor button. Defaults tofalse
.<Editor plugins={{ clearEditor: true }} />
-
history
:boolean
- Enables undo/redo history. Defaults tofalse
.<Editor plugins={{ history: true }} />
-
selectionAlwaysOnDisplay
:boolean
- Keeps the selection visible. Defaults tofalse
.<Editor plugins={{ selectionAlwaysOnDisplay: true }} />
-
richText
:boolean
- Enables rich text editing mode. Required for most rich text features. Defaults tofalse
.<Editor plugins={{ richText: true }} />
-
toolbar
:boolean
- Displays the toolbar. RequiresrichText: true
. Defaults tofalse
.<Editor plugins={{ richText: true, toolbar: true }} />
-
shortcuts
:boolean
- Enables keyboard shortcuts. RequiresrichText: true
. Defaults tofalse
.<Editor plugins={{ richText: true, shortcuts: true }} />
-
markdownShortcut
:boolean
- Enables markdown shortcuts. RequiresrichText: true
. Defaults tofalse
.<Editor plugins={{ richText: true, markdownShortcut: true }} />
-
codeHighlight
:boolean
- Enables code syntax highlighting. RequiresrichText: true
. Defaults tofalse
.<Editor plugins={{ richText: true, codeHighlight: true }} />
-
list
:boolean
- Enables lists (bulleted and numbered). RequiresrichText: true
. Defaults tofalse
.<Editor plugins={{ richText: true, list: true }} />
-
checkList
:boolean
- Enables checklists. RequiresrichText: true
. Defaults tofalse
.<Editor plugins={{ richText: true, checkList: true }} />
-
table
:object
- Enables tables. RequiresrichText: true
. Defaults to{ enabled: false }
.-
enabled
:boolean
- Enables table functionality. -
cellMerge
:boolean
- Enables cell merging. -
cellBackgroundColor
:boolean
- Enables cell background color. -
horizontalScroll
:boolean
- Enables horizontal scrolling for tables.
<Editor plugins={{ richText: true, table: { enabled: true, cellMerge: true, cellBackgroundColor: true, horizontalScroll: true, }, }} />
-
-
tableCellResizer
:boolean
- Enables table cell resizing. Requirestable: { enabled: true }
. Defaults tofalse
.<Editor plugins={{ richText: true, table: { enabled: true }, tableCellResizer: true, }} />
-
horizontalRule
:boolean
- Enables horizontal rules. RequiresrichText: true
. Defaults tofalse
.<Editor plugins={{ richText: true, horizontalRule: true }} />
-
tabFocus
:boolean
- Enables tab focus. RequiresrichText: true
. Defaults tofalse
.<Editor plugins={{ richText: true, tabFocus: true }} />
-
tabIndentation
:boolean
- Enables tab indentation. RequiresrichText: true
. Defaults tofalse
.<Editor plugins={{ richText: true, tabIndentation: true }} />
-
images
:boolean
- Enables image support. RequiresrichText: true
. Defaults tofalse
.<Editor plugins={{ richText: true, images: true }} />
-
inlineImage
:boolean
- Enables inline image support. RequiresrichText: true
. Defaults tofalse
.<Editor plugins={{ richText: true, inlineImage: true }} />
-
link
:object
- Enables link support. RequiresrichText: true
. Defaults to{ enabled: false }
.-
enabled
:boolean
- Enables link functionality. -
hasAttributes
:boolean
- Enables link attributes.
<Editor plugins={{ richText: true, link: { enabled: true, hasAttributes: true } }} />
-
-
poll
:boolean
- Enables poll support. RequiresrichText: true
. Defaults tofalse
.<Editor plugins={{ richText: true, poll: true }} />
-
twitter
:boolean
- Enables Twitter embed support. RequiresrichText: true
. Defaults tofalse
.<Editor plugins={{ richText: true, twitter: true }} />
-
youtube
:boolean
- Enables YouTube embed support. RequiresrichText: true
. Defaults tofalse
.<Editor plugins={{ richText: true, youtube: true }} />
-
figma
:boolean
- Enables Figma embed support. RequiresrichText: true
. Defaults tofalse
.<Editor plugins={{ richText: true, figma: true }} />
-
clickableLink
:boolean
- Enables clickable links. RequiresrichText: true
. Defaults tofalse
.<Editor plugins={{ richText: true, clickableLink: true }} />
-
equations
:boolean
- Enables equation support. RequiresrichText: true
. Defaults tofalse
.<Editor plugins={{ richText: true, equations: true }} />
-
excalidraw
:boolean
- Enables Excalidraw support. RequiresrichText: true
. Defaults tofalse
.<Editor plugins={{ richText: true, excalidraw: true }} />
-
collapsible
:boolean
- Enables collapsible sections. RequiresrichText: true
. Defaults tofalse
.<Editor plugins={{ richText: true, collapsible: true }} />
-
pageBreak
:boolean
- Enables page breaks. RequiresrichText: true
. Defaults tofalse
.<Editor plugins={{ richText: true, pageBreak: true }} />
-
layout
:boolean
- Enables layout features. RequiresrichText: true
. Defaults tofalse
.<Editor plugins={{ richText: true, layout: true }} />
-
dragDropPaste
:boolean
- Enables drag and drop and paste functionality. Defaults tofalse
.<Editor plugins={{ dragDropPaste: true }} />
-
componentPicker
:boolean
- Enables component picker. Defaults tofalse
.<Editor plugins={{ componentPicker: true }} />
-
emojiPicker
:boolean
- Enables emoji picker. Defaults tofalse
.<Editor plugins={{ emojiPicker: true }} />
-
autoEmbed
:boolean
- Enables auto-embedding of links. Defaults tofalse
.<Editor plugins={{ autoEmbed: true }} />
-
mentions
:object
- Enables mentions.-
enabled
:boolean
- Enables mentions functionality. -
fetchMentions
:(query: string) => Promise<Mention[]>
- Function to fetch mention data. -
onMentionSelect
:(mention: Mention) => void
- Callback when a mention is selected. -
renderMentionOption
:(mention: Mention, isSelected: boolean) => JSX.Element
- Custom rendering for mention options.
<Editor plugins={{ mentions: { enabled: true, fetchMentions: async (query) => { // Implement your mention fetching logic here const response = await fetch(`/api/users?search=${query}`); return response.json(); }, onMentionSelect: (mention) => { console.log("Mention selected:", mention); }, renderMentionOption: (mention, isSelected) => ( <div style={{ backgroundColor: isSelected ? "#eee" : "transparent", padding: "5px", }} > <div style={{ display: "flex", alignItems: "center" }}> <img src={mention.meta.imageUrl || "/default-avatar.png"} style={{ width: "24px", height: "24px", borderRadius: "50%", marginRight: "8px", }} /> <div> <div style={{ fontWeight: "bold" }}>{mention.meta.name}</div> <div style={{ fontSize: "12px", color: "#666" }}> {mention.meta.email} </div> </div> </div> </div> ), }, }} />
-
-
emojis
:boolean
- Enables emojis. Defaults tofalse
.<Editor plugins={{ emojis: true }} />
-
hashtag
:boolean
- Enables hashtags. Defaults tofalse
.<Editor plugins={{ hashtag: true }} />
-
keywords
:boolean
- Enables keywords. Defaults tofalse
.<Editor plugins={{ keywords: true }} />
-
speechToText
:boolean
- Enables speech-to-text. Defaults tofalse
.<Editor plugins={{ speechToText: true }} />
-
autoLink
:boolean
- Enables auto-linking. Defaults tofalse
.<Editor plugins={{ autoLink: true }} />
-
collaboration
:object
- Enables collaborative editing.-
enabled
:boolean
- Enables collaboration functionality. -
providerFactory
:() => any
- Factory function to create a collaboration provider.
<Editor plugins={{ collaboration: { enabled: true, providerFactory: () => { // Example WebSocket provider setup const provider = new WebsocketProvider( "wss://your-collaboration-server.com", "document-id", yourYjsDocument ); return provider; }, }, }} />
-
-
comment
:object
- Enables commenting.-
enabled
:boolean
- Enables comment functionality. -
providerFactory
:() => any
- Factory function to create a comment provider.
<Editor plugins={{ comment: { enabled: true, providerFactory: () => { // Implement your comment provider factory here return { addComment: (thread, message) => { // Your implementation }, resolveComment: (threadId) => { // Your implementation }, }; }, }, }} />
-
-
maxLength
:object
- Sets a maximum length for the editor content.-
enabled
:boolean
- Enables the max length restriction. -
length
:number
- The maximum allowed length.
<Editor plugins={{ maxLength: { enabled: true, length: 200 } }} />
-
-
characterLimit
:object
- Sets a character limit for the editor content.-
enabled
:boolean
- Enables the character limit. -
charset
:"UTF-8" | "UTF-16"
- The character set to use. -
maxLength
:number
- The maximum allowed character length.
<Editor plugins={{ characterLimit: { enabled: true, maxLength: 100, charset: "UTF-8" }, }} />
-
-
autocomplete
:boolean
- Enables autocomplete functionality. Defaults tofalse
.<Editor plugins={{ autocomplete: true }} />
-
treeView
:boolean
- Enables a tree view of the editor content. Defaults tofalse
.<Editor plugins={{ treeView: true }} />
-
tableOfContents
:boolean
- Enables a table of contents. Defaults tofalse
.<Editor plugins={{ tableOfContents: true }} />
-
contextMenu
:boolean
- Enables a context menu. Defaults tofalse
.<Editor plugins={{ contextMenu: true }} />
-
specialText
:boolean
- Enables special text handling. Defaults tofalse
.<Editor plugins={{ specialText: true }} />
-
actions
:object
- Enables actions.-
enabled
:boolean
- Enables actions functionality. -
preserveNewLinesInMarkdown
:boolean
- Preserves new lines in markdown.
<Editor plugins={{ actions: { enabled: true, preserveNewLinesInMarkdown: true } }} />
-
-
floatingLinkEditor
:boolean
- Enables the floating link editor. RequiresrichText: true
andlink: { enabled: true }
. Defaults tofalse
.<Editor plugins={{ richText: true, link: { enabled: true }, floatingLinkEditor: true, }} />
-
floatingTextFormatToolbar
:boolean
- Enables the floating text format toolbar. RequiresrichText: true
andtoolbar: true
. Defaults tofalse
.<Editor plugins={{ richText: true, toolbar: true, floatingTextFormatToolbar: true }} />
-
draggableBlock
:boolean
- Enables draggable blocks. RequiresrichText: true
. Defaults tofalse
.<Editor plugins={{ richText: true, draggableBlock: true }} />
-
codeActionMenu
:boolean
- Enables the code action menu. RequiresrichText: true
andcodeHighlight: true
. Defaults tofalse
.<Editor plugins={{ richText: true, codeHighlight: true, codeActionMenu: true }} />
-
tableHoverActions
:boolean
- Enables table hover actions. RequiresrichText: true
andtable: { enabled: true }
. Defaults tofalse
.<Editor plugins={{ richText: true, table: { enabled: true }, tableHoverActions: true, }} />
-
tableCellActionMenu
:boolean
- Enables the table cell action menu. RequiresrichText: true
andtable: { enabled: true }
. Defaults tofalse
.<Editor plugins={{ richText: true, table: { enabled: true }, tableCellActionMenu: true, }} />
react-lexify provides a flexible way to override the default class names used for styling the editor and toolbar components. This allows developers to fully customize the appearance using their own CSS framework (e.g., Tailwind, Bootstrap) or design system.
You can override structural wrapper classes using the classOverrides
prop. These styles affect how the editor container and internal wrappers are rendered and styled.
<Editor
classOverrides={{
editorContainer: "w-full rounded-md border",
editorScroller: "max-h-[400px] overflow-y-auto",
editorContent: "min-h-[250px] bg-white px-3 py-2",
editorShell: "bg-background shadow-sm",
floatingTextFormatToolbar: {
container: "flex py-2 px-1 gap-2 h-12",
},
}}
/>
Key | Description |
---|---|
editorContainer |
Outermost wrapper of the editor |
editorScroller |
Scrollable area of the editor |
editorContent |
Core editable content area |
editorShell |
Shell that wraps the editor (for background, border) |
floatingTextFormatToolbar |
Class for the floating text toolbar container |
plainText |
Plain text editor wrapper |
richTextPlugin |
Rich text editor plugin wrapper |
treeView |
Tree view panel if enabled |
✅ Use
classOverrides
for layout-level styling
<Editor
initialConfig={{
theme: {
paragraph: "my-paragraph-style",
heading: {
h1: "text-4xl font-bold",
h2: "text-3xl font-semibold",
},
list: {
ul: "list-disc pl-5",
ol: "list-decimal pl-5",
},
quote: "border-l-4 pl-4 italic text-muted",
link: "text-blue-500 underline",
text: {
bold: "font-bold",
italic: "italic",
underline: "underline",
strikethrough: "line-through",
},
},
}}
/>
✅ Use
initialConfig.theme
for formatting nodes like headings, lists, links, etc.
You can customize the toolbar in a structured and type-safe way using ToolbarStyleConfig. This supports class overrides and icon or label customization:
Property | Type | Description |
---|---|---|
rootClass |
string | (defaultClass: string) => string |
Main wrapper class for the toolbar. |
buttonClasses |
Partial<Record<ToolbarButtonKey, string | (defaultClass: string) => string>> |
Class overrides for each individual button. |
iconClasses |
Partial<Record<ToolbarButtonKey | ToolbarDropdownKey, string | (defaultClass) => string>> |
Icon-level class override per button or dropdown. |
dropdownItemClasses |
Partial<Record<ToolbarDropdownKey, string | (defaultClass) => string>> |
Class override for each dropdown item (like h1, bullet list, align options, etc). |
labelOverrides |
Partial<Record<ToolbarDropdownKey, string>> |
Change default labels shown in dropdowns (e.g., "h1" to "Heading One"). |
Key | Description |
---|---|
bold |
Toggle bold text |
italic |
Toggle italic text |
underline |
Toggle underline |
code |
Toggle inline code |
link |
Create or edit a link |
strikethrough |
Toggle strikethrough |
subscript |
Toggle subscript |
superscript |
Toggle superscript |
highlight |
Highlight text |
clearFormatting |
Remove all formatting |
undo |
Undo last action |
redo |
Redo last undone action |
insertCodeBlock |
Insert a code block |
insertLink |
Insert a link |
lowercase |
Transform to lowercase |
uppercase |
Transform to uppercase |
capitalize |
Capitalize text |
fontColor |
Change font color |
bgColor |
Change background color |
insert |
Open insert menu |
blockControls |
Block-level formatting |
alignment |
Align text |
fontFamily |
Change font family |
fontSize |
Change font size |
moreStyles |
Open more styles dropdown |
horizontalRule |
Insert horizontal line |
pageBreak |
Insert page break |
image |
Insert image |
inlineImage |
Insert inline image |
gif |
Insert GIF |
excalidraw |
Insert drawing canvas |
table |
Insert table |
poll |
Insert poll |
columns |
Insert column layout |
equation |
Insert math equation |
sticky |
Insert sticky note |
collapsible |
Insert collapsible section |
Key | Description |
---|---|
paragraph |
Paragraph text |
h1 |
Heading level 1 |
h2 |
Heading level 2 |
h3 |
Heading level 3 |
bullet |
Bullet list |
number |
Numbered list |
check |
Checklist |
quote |
Blockquote |
code |
Code block |
leftAlign |
Align text left |
centerAlign |
Center align text |
rightAlign |
Align text right |
justifyAlign |
Justify text |
startAlign |
Align to start (for RTL) |
endAlign |
Align to end (for RTL) |
outdent |
Outdent list level |
indent |
Indent list level |
<Editor
plugins={{
toolbar: true,
}}
toolbarStyle={{
rootClass: "flex gap-2 p-2 bg-white shadow",
buttonClasses: {
bold: "text-bold hover:bg-gray-100",
},
iconClasses: {
italic: "text-blue-500",
},
dropdownItemClasses: {
h1: "font-bold text-lg",
},
labelOverrides: {
h1: "Heading One",
h2: "Heading Two",
},
}}
/>
This gives you full flexibility to style, override, or relabel any part of the toolbar to match your design system.
Below is a comprehensive example showing most of the available configuration options:
import React from "react";
import Editor from "react-lexify";
export default function FullFeaturedEditor() {
return (
<Editor
placeholder="Write something amazing..."
initialConfig={{
namespace: "MyAdvancedEditor",
theme: {
// Your custom theme
},
onError: (error) => {
console.error("Editor error:", error);
},
}}
plugins={{
// Core plugins
autoFocus: true,
clearEditor: true,
history: true,
selectionAlwaysOnDisplay: true,
// Rich text plugins
richText: true,
toolbar: true,
shortcuts: true,
markdownShortcut: true,
codeHighlight: true,
list: true,
checkList: true,
// Table features
table: {
enabled: true,
cellMerge: true,
cellBackgroundColor: true,
horizontalScroll: true,
},
tableCellResizer: true,
// More rich text features
horizontalRule: true,
tabFocus: true,
tabIndentation: true,
// Content types
images: true,
inlineImage: true,
link: {
enabled: true,
hasAttributes: true,
},
poll: true,
twitter: true,
youtube: true,
figma: true,
clickableLink: true,
equations: true,
excalidraw: true,
collapsible: true,
pageBreak: true,
layout: true,
// Interactive plugins
dragDropPaste: true,
componentPicker: true,
emojiPicker: true,
autoEmbed: true,
mentions: {
enabled: true,
fetchMentions: async (query) => {
const response = await fetch(`/api/users?search=${query}`);
return response.json();
},
},
emojis: true,
hashtag: true,
keywords: true,
speechToText: true,
autoLink: true,
// Collaborative features
collaboration: {
enabled: true,
providerFactory: () => {
// Your collaboration provider setup
return null; // Replace with actual provider
},
},
comment: {
enabled: true,
providerFactory: () => {
// Your comment provider setup
return null; // Replace with actual provider
},
},
// UI & behavior
maxLength: {
enabled: true,
length: 5000,
},
characterLimit: {
enabled: false,
},
autocomplete: true,
treeView: true,
tableOfContents: true,
contextMenu: true,
specialText: true,
actions: {
enabled: true,
preserveNewLinesInMarkdown: true,
},
// Floating UI
floatingLinkEditor: true,
floatingTextFormatToolbar: true,
draggableBlock: true,
codeActionMenu: true,
tableHoverActions: true,
tableCellActionMenu: true,
}}
/>
);
}
interface Mention {
id: string;
displayName: string;
meta?: {
name?: string;
email?: string;
imageUrl?: string;
phoneNumber?: number;
[key: string]: any; // Additional custom properties
};
[key: string]: any; // Any additional top-level properties
}
import React from "react";
import Editor from "react-lexify";
export default function BasicEditor() {
return (
<Editor
placeholder="Start typing..."
plugins={{
richText: true,
toolbar: true,
history: true,
autoFocus: true,
}}
/>
);
}
import React from "react";
import Editor from "react-lexify";
import { WebsocketProvider } from "y-websocket";
import * as Y from "yjs";
export default function CollaborativeEditor() {
const setupCollaboration = () => {
const doc = new Y.Doc();
const provider = new WebsocketProvider(
"wss://your-collaboration-server.com",
"document-123",
doc
);
return provider;
};
return (
<Editor
placeholder="Collaborate in real-time..."
plugins={{
richText: true,
toolbar: true,
history: true,
collaboration: {
enabled: true,
providerFactory: setupCollaboration,
},
}}
/>
);
}
import React from "react";
import Editor from "react-lexify";
export default function SocialEditor() {
return (
<Editor
placeholder="What's on your mind?"
plugins={{
richText: true,
toolbar: true,
mentions: {
enabled: true,
fetchMentions: async (query) => {
// Fetch users from your API
return [
{
id: "1",
displayName: "John Doe",
meta: {
name: "John",
email: "john@example.com",
imageUrl: "/john.jpg",
},
},
{
id: "2",
displayName: "Jane Smith",
meta: {
name: "Jane",
email: "jane@example.com",
imageUrl: "/jane.jpg",
},
},
].filter((user) =>
user.displayName.toLowerCase().includes(query.toLowerCase())
);
},
},
hashtag: true,
emojis: true,
images: true,
link: { enabled: true },
youtube: true,
twitter: true,
}}
/>
);
}
The library is built with TypeScript and includes type definitions for all components and configurations:
import { Editor, EditorPluginConfig, Mention } from "react-lexify";
react-lexify supports all modern browsers:
- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add some amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
This project is licensed under the MIT License.
Disclaimer: This project (
react-lexify
) is built on top of Lexical, an open-source framework developed and maintained by Meta Platforms, Inc.. It is an independent project not affiliated with or endorsed by Meta. Portions of the code and plugin structure are adapted from the Lexical Playground, which is also licensed under the MIT License.
by rhythmsapkota GitHub Profile
Contributions, issues, and suggestions are welcome!