本文へ移動
cccskills
無料GitHub で公開

web-editor-lexical

Extensible text editor framework by Meta. Use when building a rich text editor on Lexical — editor setup, custom nodes, commands, transforms, and serialization.

インストール方法を見る

含まれるファイル(6)

  • SKILL.md13.6 KB
  • examples/core.md13.3 KB
  • examples/custom-nodes.md13.8 KB
  • examples/serialization.md10.2 KB
  • metadata.yaml467 B
  • reference.md6.8 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Lexical Editor Patterns

Quick Guide: Lexical is an editor framework rather than an editor: the core gives you a node tree, a selection model, a reconciler, a command bus and an update lifecycle, and everything else is a plugin. EditorState is immutable, so every read and every mutation happens inside an editor.update() or editor.read() closure, and the $-prefixed functions are the ones that require that context. Extend the tree through ElementNode, TextNode or DecoratorNode, and react to content through transforms rather than listeners. Current: v0.42.x, pre-1.0 — APIs still move between minors.

Detailed Resources:

  • examples/core.md — editor setup, plugins with cleanup, toolbars, transforms, persistence
  • examples/custom-nodes.md — ElementNode, TextNode and DecoratorNode classes, the NodeState and $config APIs
  • examples/serialization.md — JSON and HTML round trips, exportDOM/importDOM, headless editors
  • reference.md — package map, command priorities, built-in commands, node hierarchy, plugin list, custom-node checklist

Which path applies

  • A React app — LexicalComposer owns the editor and every plugin is a child component reaching it through useLexicalComposerContext(). Start at examples/core.md.
  • A server or a build step, with no DOM — createHeadlessEditor from @lexical/headless runs the same node classes for search indexing, email rendering and content transforms. Register the same nodes as the client. See examples/serialization.md.
  • Adding a content type — the work is a node class plus its registration, and the branch that matters is which base node it extends. See the decision framework below and examples/custom-nodes.md.

<critical_requirements>

Before writing Lexical code

Call $-prefixed functions inside an editor.update() or editor.read() closure. $getRoot, $getSelection, $createTextNode and their siblings read the active editor state from a context that only exists inside those closures; outside one they throw at runtime, and nothing catches it at compile time.

Register every custom node in initialConfig.nodes. An unregistered node throws or silently drops content the moment the editor meets it, including on deserialization of previously saved documents.

Return the unsubscribe function from the useEffect that registers a command, transform or listener. Every register* method hands one back, and dropping it leaks a listener per render.

Open every transform with a precondition that the mutation makes false. A transform that mutates its target unconditionally marks the node dirty, which re-triggers the transform and freezes the editor.

</critical_requirements>


Auto-detection: Lexical, @lexical/react, @lexical/rich-text, @lexical/list, @lexical/code, @lexical/link, @lexical/html, @lexical/headless, LexicalComposer, EditorState, LexicalNode, ElementNode, TextNode, DecoratorNode, createCommand, dispatchCommand, registerCommand, COMMAND_PRIORITY, $getRoot, $getSelection, $createParagraphNode, $createTextNode, RichTextPlugin, OnChangePlugin, HistoryPlugin, useLexicalComposerContext, editor.update, editor.read, registerNodeTransform, exportJSON, importJSON, exportDOM, importDOM, NodeState, createState

Applies to:

  • Rich text editing with custom formatting and embedded content
  • Custom content types — mentions, embeds, callouts, polls — as node classes
  • Structured document output rather than an HTML string
  • Server-side or build-time processing of editor content
  • Collaborative editing, where Lexical supplies the binding point

Handled elsewhere:

  • Visual design of the editor — the theme maps class names onto nodes, and what those classes contain is settled by whatever owns styling
  • Where the serialized document goes — the editor hands back JSON, and the transport and store are not its concern
  • Real-time sync between clients — Lexical exposes the state to bind, and the sync layer itself is a separate concern
  • Sanitizing HTML entering or leaving the editor — $generateNodesFromDOM trusts what it is given

<philosophy>

Lexical ships a core and no editor. The tree, selection, reconciler, command bus and update lifecycle are the product; toolbars, lists, links, embeds and formatting are all plugins, including the ones Meta writes.

EditorState is immutable. The editor holds a frozen snapshot. editor.update() clones it, applies the closure's changes, and reconciles the difference to the DOM — which is why a stale read outside a closure has no state to read and throws.

A plugin is a React component. It renders as a child of <LexicalComposer>, reaches the editor through useLexicalComposerContext(), and registers its commands, transforms and listeners in a useEffect that returns their unsubscribes. Many plugins render null.

Commands are the bus. Typed commands with priority-ordered listeners let one plugin intercept or augment another's behaviour without either knowing about the other.

Content is typed nodes. A new kind of content is a new node class, not a new attribute.

Lexical is pre-1.0, so a project that needs a frozen API surface should weigh that before adopting it. Weigh the node model too: a DecoratorNode is a real node in the tree, so it serializes and moves with the content around it. An editor whose requirement is a purely visual overlay — highlights or annotations that must never enter the saved document — is asking for something Lexical's decorators do not do.

</philosophy>

<decision_framework>

Which node type to extend

Does the content contain child nodes?
├─ YES → ElementNode (paragraphs, blockquotes, callouts)
└─ NO → Is it text carrying extra formatting or behaviour?
    ├─ YES → TextNode (coloured text, mentions)
    └─ NO → Is it an embedded component (image, video, widget)?
        └─ YES → DecoratorNode, whose decorate() returns the component

Plugin, transform or listener

Does the reaction modify nodes?
├─ YES → Transform — runs before reconciliation, so one DOM update covers it
└─ NO → Is it observing state?
    ├─ YES → registerUpdateListener, which runs after reconciliation
    └─ NO → A command, for user actions and toolbar clicks

Which command priority

Base editor behaviour?          → COMMAND_PRIORITY_EDITOR (0)
An ordinary plugin?             → COMMAND_PRIORITY_LOW (1) or _NORMAL (2)
Must override other plugins?    → COMMAND_PRIORITY_HIGH (3), as table navigation does
Nothing else may win?           → COMMAND_PRIORITY_CRITICAL (4)

Higher runs first, and returning true stops propagation to everything below. Full table in reference.md.

</decision_framework>


<patterns>

Core patterns

Pattern 1: React editor setup

LexicalComposer takes one initialConfig and wraps the plugins as children. Define the config outside the component so it is not rebuilt every render.

const initialConfig = {
  namespace: "MyEditor",
  theme, // class names per node type
  onError, // rethrow, or report — see red flags
  nodes: [HeadingNode, QuoteNode, ListNode, ListItemNode], // every node the plugins need
};

<LexicalComposer initialConfig={initialConfig}>
  <RichTextPlugin contentEditable={<ContentEditable />} ErrorBoundary={LexicalErrorBoundary} />
  <HistoryPlugin />
</LexicalComposer>;

Full code: examples/core.md


Pattern 2: The update lifecycle

editor.update() mutates, editor.read() observes, and the $ prefix marks the functions that need one of them open.

editor.update(() => {
  const paragraph = $createParagraphNode();
  paragraph.append($createTextNode("Hello world"));
  $getRoot().append(paragraph);
});

editor.read(() => $getRoot().getTextContent());

Updates batch synchronously and reconcile asynchronously; pass { discrete: true } when the DOM has to be committed before the next statement reads it.

Full code: examples/serialization.md


Pattern 3: The command system

Commands carry a typed payload and are dispatched from anywhere. Listeners register at a priority and return true to consume the command.

export const INSERT_IMAGE_COMMAND: LexicalCommand<{
  src: string;
  alt: string;
}> = createCommand("INSERT_IMAGE_COMMAND");

editor.dispatchCommand(INSERT_IMAGE_COMMAND, {
  src: "/image.png",
  alt: "Photo",
});

Full code: examples/core.md


Pattern 4: A plugin as a React component

The plugin reads the editor from context and owns its registrations for the life of the component.

export function ToolbarPlugin() {
  const [editor] = useLexicalComposerContext();
  return (
    <button type="button" onClick={() => editor.dispatchCommand(FORMAT_TEXT_COMMAND, "bold")}>
      Bold
    </button>
  );
}

Full code: examples/core.md


Pattern 5: Node transforms

A transform runs on every dirty node of its type before reconciliation, which makes it the cheapest place to react to content. The precondition is what stops it re-triggering itself.

editor.registerNodeTransform(TextNode, (textNode) => {
  const text = textNode.getTextContent();
  if (text.length > 0 && text[0] !== text[0].toUpperCase()) {
    textNode.setTextContent(text[0].toUpperCase() + text.slice(1));
  }
});

Leaf nodes transform first, then elements, then the root, and the whole cascade produces a single DOM reconciliation.

Full code: examples/core.md


Pattern 6: Custom nodes

Every custom node needs static getType(), static clone(), createDOM(), updateDOM(), exportJSON() / static importJSON(), and an entry in initialConfig.nodes. Private properties take a double-underscore prefix so minifiers leave them alone, and every one of them has to be JSON-serializable.

export class ImageNode extends DecoratorNode<JSX.Element> {
  __src: string;

  static getType(): string {
    return "image";
  }
  static clone(node: ImageNode): ImageNode {
    return new ImageNode(node.__src, node.__key);
  }
  updateDOM(): boolean {
    return false; // the existing element can be reused
  }
  decorate(): JSX.Element {
    return <img src={this.__src} alt="" />;
  }
}

Reach a node through its $createXxxNode() factory rather than new, so $applyNodeReplacement can run.

Full code: examples/custom-nodes.md


Pattern 7: Serialization

JSON is the persistence format — it round-trips the whole tree including custom node properties. HTML is for display and interop, and it is lossy.

const jsonString = JSON.stringify(editor.getEditorState().toJSON());

editor.setEditorState(editor.parseEditorState(jsonString).clone(null));

editor.read(() => $generateHtmlFromNodes(editor, null));

Full code: examples/serialization.md

</patterns>

<red_flags>

Red flags

Breaks at runtime:

  • A $-function called outside editor.update() / editor.read() — throws, with no compile-time warning — move the call inside the closure
  • A custom node missing from initialConfig.nodes — the editor throws or drops the content when it meets the node — register it in the same place the plugin is added
  • A transform with no precondition — the mutation marks the node dirty, re-triggering the transform until the editor freezes — guard on the condition the mutation removes
  • A register* call whose unsubscribe is dropped — one leaked listener per render and stale references after unmount — return it from useEffect
  • A node property holding a function, Map or Set — serialization breaks quietly — keep every property JSON-serializable
  • Two nodes sharing a getType() string — deserialization resolves the wrong class — namespace the type
  • new MyNode() instead of $createMyNode() — bypasses $applyNodeReplacement, so any registered replacement never runs
  • A single-underscore node property — minifiers mangle it — use __
  • Direct DOM mutation of editor content — bypasses the reconciler and desyncs state from DOM — go through nodes and commands
  • editor.update() called from inside an update listener — breaks undo/redo history and forces an extra render — use a transform
  • console.log in onError with no rethrow — swallows every editor error — rethrow or report it

Surprising behaviour:

  • Updates batch synchronously but reconcile asynchronously, so a DOM measurement taken straight after an update reads the old layout unless the update passed { discrete: true }
  • setEditorState steals focus unless the state is passed through editorState.clone(null)
  • DecoratorNode.decorate() renders its component outside the normal React tree, so state and context in it need care
  • onError that does not rethrow lets Lexical attempt its own recovery, which can mask a broken node class
  • TextNode modes change deletion: "token" makes the text an immutable chip, "segmented" deletes it word by word
  • CSS transition never fires on node removal, because the reconciler removes the element rather than hiding it
  • The NodeState API (v0.26+) is experimental and can change without an extended deprecation

</red_flags>

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Hugging Face Inference SDK patterns for TypeScript/Node.js — InferenceClient setup, chat completion, text generation, streaming, embeddings, image generation, audio transcription, translation, summarization, and Inference Endpoints

日本語の概要は準備中です。原文の説明を表示しています。

agents-inc/skills242026年9月8日 更新

LiteLLM proxy server setup, TypeScript client patterns via OpenAI SDK, model routing, fallbacks, load balancing, spend tracking, virtual keys, and production deployment

日本語の概要は準備中です。原文の説明を表示しています。

agents-inc/skills242026年9月8日 更新

Serverless GPU compute platform for AI model deployment — web endpoints, GPU functions, model serving, and TypeScript client patterns

日本語の概要は準備中です。原文の説明を表示しています。

agents-inc/skills242026年9月8日 更新

Local LLM inference with the Ollama JavaScript client -- chat, streaming, tool calling, vision, embeddings, structured output, model management, and OpenAI-compatible endpoint

日本語の概要は準備中です。原文の説明を表示しています。

agents-inc/skills242026年9月8日 更新

Replicate SDK patterns for TypeScript/Node.js -- client setup, predictions, streaming, webhooks, file handling, model versioning, deployments, and training

日本語の概要は準備中です。原文の説明を表示しています。

agents-inc/skills242026年9月8日 更新

Together AI SDK patterns for TypeScript — client setup, chat completions, streaming, structured output, function calling, embeddings, image generation, fine-tuning, and OpenAI-compatible endpoints

日本語の概要は準備中です。原文の説明を表示しています。

agents-inc/skills242026年9月8日 更新

agents-inc のスキルをすべて見る

このスキルの問題を報告する