Skip to content

docs: add Decorators concept guide#8696

Open
WilliamK112 wants to merge 3 commits into
facebook:mainfrom
WilliamK112:wk/docs-decorators-concept
Open

docs: add Decorators concept guide#8696
WilliamK112 wants to merge 3 commits into
facebook:mainfrom
WilliamK112:wk/docs-decorators-concept

Conversation

@WilliamK112

Copy link
Copy Markdown

Addresses part of #2845.

This adds a dedicated Concepts page for Decorators, following the maintainer suggestion in #2845 to bring the old decorator-node guidance into the website docs under Concepts.

The new page explains:

  • when to use a DecoratorNode instead of TextNode or ElementNode
  • inline vs block decorators
  • registration through editor config and insertion commands
  • state/serialization guidance with NodeState
  • the createDOM() / decorate() rendering model

It also links the existing DecoratorNode section in concepts/nodes to the new page and adds the page to the Concepts sidebar.

Validation:

  • pnpm exec prettier --check packages/lexical-website/docs/concepts/decorators.mdx packages/lexical-website/docs/concepts/nodes.mdx packages/lexical-website/sidebars.js
  • pnpm --filter @lexical/website run tsc
  • pnpm --filter @lexical/website run build

Note: the website build succeeded. It emitted an existing /docs/api/ HTML minifier warning unrelated to this new Decorators page.

@meta-cla

meta-cla Bot commented Jun 14, 2026

Copy link
Copy Markdown

Hi @WilliamK112!

Thank you for your pull request and welcome to our community.

Action Required

In order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you.

Process

In order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA.

Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with CLA signed. The tagging process may take up to 1 hour after signing. Please give it that time before contacting us about it.

If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks!

@vercel

vercel Bot commented Jun 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
lexical Ready Ready Preview, Comment Jun 18, 2026 8:05am
lexical-playground Ready Ready Preview, Comment Jun 18, 2026 8:05am

Request Review

@potatowagon potatowagon left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed by Navi (Tater Thoughts Bobblehead) on behalf of @potatowagon.

Assessment: LGTM ✅ — Pure documentation addition, well-structured and accurate.

What I checked:

  • Content accuracy: The guide correctly describes DecoratorNode behavior — decorate() returning framework-specific values, createDOM() as the host, updateDOM() returning false for stable hosts, inline-by-default semantics. All consistent with the Lexical source.
  • Code examples: The VideoNode examples use correct APIs ($insertNodes, createCommand, COMMAND_PRIORITY_EDITOR, useLexicalComposerContext). The NodeState example correctly uses createState, $getState, $setState, and $create — matching the new NodeState/$config protocol from recent PRs.
  • Cross-references: Links to /docs/concepts/node-state and the existing DecoratorNode section in nodes.mdx are valid. Sidebar placement (after nodes, before node-replacement) is logical.
  • No library code changes — only packages/lexical-website/ files touched.
  • No www compat concerns (docs only).

CI status: Vercel previews pass (both website and playground deploy fine). CLA check is failing — author needs to sign the Meta CLA before this can merge.

Minor suggestions (non-blocking):

  1. The isInline(): false return type annotation is redundant since the method body already returns false (TypeScript infers it), but it does serve as documentation — fine either way.
  2. Consider adding a brief mention of setDecoratorClassNames() for styling the host element, since newcomers often struggle with styling decorator boundaries.

Ready to merge once CLA is signed.

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jun 14, 2026
@meta-cla

meta-cla Bot commented Jun 14, 2026

Copy link
Copy Markdown

Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks!

@etrepum etrepum left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good start, a few additional things to cover (some of these are unreleased but in main):

  • Current lexical convention is to put all configuration and behavior in extensions, not directly in an editor config (which also implies using LexicalExtensionComposer)
  • DecoratorNode can be framework independent if the decorate method returns null (the default) and all work happens in createDOM/updateDOM and/or managed by DOMRenderExtension hooks or a mutation listener (HorizontalRuleNode from @lexical/extension is an example of this)
  • There's probably other stuff to cover with named slots which adds a lot of potential to DecoratorNode
  • DOMImportExtension should be mentioned in preference to importDOM, which will be deprecated eventually because the two import methods can't be used at the same time
  • It's an advanced feature, but NodeState can store any kind of data, given a suitable StateValueConfig
  • It's also recently possible to use arbitrary DOM inside or around ElementNode with a combination of DOMSlot and something like mutation listeners, like the ReviewExtension and its associated ReviewNode in the playground
@WilliamK112

Copy link
Copy Markdown
Author

Thanks for the detailed pointers. I pushed a follow-up commit (5397663) that tightens the advanced DOM ownership section around the current ReviewExtension / ReviewNode pattern: it now calls out using getDOMSlot() for editable content inside custom UI, while the extension tracks live nodes with a mutation listener and renders the surrounding review UI.

The earlier follow-up already covered the other items you mentioned: extension-first registration, framework-independent decorators via decorate() === null, DOMImportExtension, named slots, and richer NodeState / StateValueConfig usage.

Validation run locally:

  • corepack pnpm exec prettier --check packages/lexical-website/docs/concepts/decorators.mdx
  • git diff --check
  • corepack pnpm -C packages/lexical-website run tsc
  • corepack pnpm run build
  • IGNORE_PEER_DEPENDENCIES=react corepack pnpm -C packages/lexical-website exec docusaurus build
@WilliamK112

Copy link
Copy Markdown
Author

Hi @etrepum — I've addressed the documentation feedback in a3b220d. Let me know if you need anything else before re-review. Thanks!

@etrepum etrepum left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR title and description should follow the pull request template

Comment on lines +59 to +100
As with other custom nodes, a decorator node must be registered before it is
used. In React, pass the node class in the editor config:

```tsx
<LexicalComposer
initialConfig={{
namespace: 'Example',
nodes: [VideoNode],
onError(error) {
throw error;
},
}}>
{/* editor UI */}
</LexicalComposer>
```

A common pattern is to pair the node with a plugin or extension that registers
commands for inserting it.

```ts
export const INSERT_VIDEO_COMMAND: LexicalCommand<string> = createCommand(
'INSERT_VIDEO_COMMAND',
);

function VideoPlugin(): null {
const [editor] = useLexicalComposerContext();

useEffect(() => {
return editor.registerCommand(
INSERT_VIDEO_COMMAND,
videoID => {
editor.update(() => {
$insertNodes([$createVideoNode(videoID)]);
});
return true;
},
COMMAND_PRIORITY_EDITOR,
);
}, [editor]);

return null;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section is still documenting the legacy config + plugin pattern, not extensions. Plugins that return null and have no actual react dependency are also not encouraged because that can be better handled with framework independent extensions. This whole example can be implemented in a single extension rather than the separate legacy config + plugin pattern.

Comment on lines +109 to +115
For newer code, prefer putting the node registration and the behavior that
goes with it in an [extension](/docs/extensions/intro) instead of spreading it
across an editor config and separate plugins. React applications can mount that
root extension with
[`LexicalExtensionComposer`](/docs/api/modules/lexical_react_LexicalExtensionComposer#lexicalextensioncomposer).
This keeps node registration, commands, transforms, mutation listeners, and
framework decorators in one composable unit.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should simply document how you would use decorators with extensions and not even provide an example of how it used to be done.

Comment on lines +167 to +169
`importDOM()` still works, but it shares the same import path as the extension
pipeline and is expected to be deprecated eventually, so avoid mixing both
approaches for the same import behavior.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"shares the same import path as the extension pipeline" doesn't make sense.

`decorate()`.

Decorator nodes also combine well with
[named slots](https://lexical.dev/docs/concepts/named-slots) when surrounding

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be a relative URL like all of the other links

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.

3 participants