In this article, I’ll share my step-by-step approach to building a full-featured Rich Text Editor in React.
The reason I chose Lexical (over Tiptap or others) is simple: speed ⚡.
But… Lexical by itself can be a bit painful to work with.
It’s low-level, verbose, and you often end up handling too much boilerplate.
That’s why I built LexKit — an open-source layer on top of Lexical.
It’s DX-friendly, type-safe, and ships with ready-to-use templates (even for shadcn).  
👉 It gives us the full power of Lexical, but with a lot more out-of-the-box features.
👉 Plug-and-play extensions.
👉 Strong type safety.  
📚 Great docs are here → lexkit.dev/docs
🛠️ Installing LexKit
First, install LexKit + Lexical in your project:
npm install @lexkit/editor
And install the required Lexical packages:
npm install lexical @lexical/react @lexical/html @lexical/markdown @lexical/list @lexical/rich-text @lexical/selection @lexical/utils
✨ Creating the Basic Editor
Let’s create a basic editor together:
import {
  createEditorSystem,
  boldExtension,
  italicExtension,
  historyExtension,
  listExtension,
  linkExtension,
  RichText,
} from "@lexkit/editor";
import "./basic-editor.css";
// 1. Define your extensions (as const for type safety)
const extensions = [
  boldExtension,
  italicExtension,
  listExtension,
  linkExtension,
  historyExtension,
] as const;
// 2. Create typed editor system
const { Provider, useEditor } = createEditorSystem<typeof extensions>();
The nice thing?
useEditor is fully type-safe — it knows exactly what commands you have.
🛠️ Toolbar Example
Here’s a simple toolbar with some text formatting actions:
// Toolbar Component - Shows basic text formatting buttons
function Toolbar() {
  const { commands, activeStates } = useEditor();
  return (
    <div className="basic-toolbar">
      <button
        onClick={() => commands.toggleBold()}
        className={activeStates.bold ? "active" : ""}
        title="Bold (Ctrl+B)"
      >
        Bold
      </button>
      <button
        onClick={() => commands.toggleItalic()}
        className={activeStates.italic ? "active" : ""}
        title="Italic (Ctrl+I)"
      >
        Italic
      </button>
      <button
        onClick={() => commands.toggleUnorderedList()}
        className={activeStates.unorderedList ? "active" : ""}
        title="Bullet List"
      >
        • List
      </button>
      <button
        onClick={() => commands.toggleOrderedList()}
        className={activeStates.orderedList ? "active" : ""}
        title="Numbered List"
      >
        1. List
      </button>
      <button
        onClick={() => commands.undo()}
        disabled={!activeStates.canUndo}
        className={!activeStates.canUndo ? "disabled" : ""}
        title="Undo (Ctrl+Z)"
      >
        ↶ Undo
      </button>
      <button
        onClick={() => commands.redo()}
        disabled={!activeStates.canRedo}
        className={!activeStates.canRedo ? "disabled" : ""}
        title="Redo (Ctrl+Y)"
      >
        ↷ Redo
      </button>
    </div>
  );
}
📝 Putting It All Together
Now, let’s use our Toolbar inside the editor provider:
// Main Component
export function BasicEditorExample() {
  return (
    <Provider extensions={extensions}>
      <div className="basic-editor">
        <Toolbar />
        <RichText
          classNames={{
            container: "basic-editor-container",
            contentEditable: "basic-content",
            placeholder: "basic-placeholder",
          }}
          placeholder="Start writing your content here..."
        />
      </div>
    </Provider>
  );
}
To style it, just copy this CSS into your project:
👉 basic-editor.css
🎉 Boom — you’ve got your first editor running!
Try it out live in the Playground.
📦 Templates
The same way we created the basic editor, you can import more extensions or use prebuilt templates:
Default Template (React + CSS):
👉 Lexkit Default TemplateShadcn-Ready Template:
👉 Lexical + Shadcn Template with LexKit
📚 Docs & Demos
Everything is documented here:
💡 With LexKit, you can build Notion-like editors, blog editors, CMS inputs, or any custom RTE — all while staying type-safe and React-friendly.
If you’re into React + TypeScript, I think you’ll love it ❤️
I'd love to hear your thoughts. Feel free to drop a comment <3



    
Top comments (1)
Feel free to share your thoughts here with me :)