Skip to content

[lexical] Chore: Change alias from type to interface for EditorThemeClasses#8190

Merged
etrepum merged 1 commit intofacebook:mainfrom
levensta:change-theme-type-to-interface
Mar 4, 2026
Merged

[lexical] Chore: Change alias from type to interface for EditorThemeClasses#8190
etrepum merged 1 commit intofacebook:mainfrom
levensta:change-theme-type-to-interface

Conversation

@levensta
Copy link
Contributor

@levensta levensta commented Mar 3, 2026

Description

This change allows you to extend the theme object type with indexable specific properties and take advantage of IDE and TypeScript hints

Test plan

Before

The EditorThemeClasses type is based on an indexed signature:

type EditorThemeClasses = {
  [key: string]: any;
}

This means that if you are using TypeScript, you can declare a theme with any properties that differ from the standard ones, but TypeScript will not know about this and will cast others properties to the any type

// Method in a custom node
createDOM(config: EditorConfig): HTMLElement {
  const element = document.createElement('span');
  // (index) EditorThemeClasses[string]: any
  addClassNamesToElement(element, config.theme.tooltip);
  return element;
}

After

You can still assign any properties to the EditorThemeClasses object, but using TypeScript, you can extend it with specific properties to target IDE hints

// Define this in the global type file in your project.
declare module 'lexical' {
  interface EditorThemeClasses {
    tooltip?: 'MyEditorTheme__tooltip';
  }
}

// Method in a custom node
createDOM(config: EditorConfig): HTMLElement {
  const element = document.createElement('span');
  // (property) EditorThemeClasses.tooltip?: "MyEditorTheme__tooltip" | undefined
  addClassNamesToElement(element, config.theme.tooltip);
  return element;
}

⚠️ This is not the best approach, but it is one of the available ones.
The best practice is to define custom themes for nodes through the Extension API. See the example in the documentation: https://lexical.dev/docs/extensions/migration#react-plug-in-without-ui

@vercel
Copy link

vercel bot commented Mar 3, 2026

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

Project Deployment Actions Updated (UTC)
lexical Ready Ready Preview, Comment Mar 4, 2026 7:30am
lexical-playground Ready Ready Preview, Comment Mar 4, 2026 7:30am

Request Review

@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 Mar 3, 2026
@levensta levensta changed the title [lexical] Feat: Change alias from type to interface for EditorThemeClasses [lexical] Chore: Change alias from type to interface for EditorThemeClasses Mar 3, 2026
@etrepum
Copy link
Collaborator

etrepum commented Mar 3, 2026

A better way forward is to move configuration of anything not built-in to extension config. These don’t have the same namespace issues, are type safe, etc. and you don’t need declaration merging to get there.

@levensta
Copy link
Contributor Author

levensta commented Mar 3, 2026

Although objects of any type can be passed to extensions, the argument of the createDOM and updateDOM methods is always typed EditorConfig, which references EditorThemeClasses for theme.

@etrepum
Copy link
Collaborator

etrepum commented Mar 3, 2026

You can get a reference to any extension from those methods with https://lexical.dev/docs/api/modules/lexical_extension#getextensiondependencyfromeditor

@levensta
Copy link
Contributor Author

levensta commented Mar 3, 2026

I see, it would look something like this

// App.tsx
type MyTheme = EditorThemeClasses & {
  tooltip?: string
}

export const editorExtension = defineExtension<
  ExtensionConfigBase,
  'My App,
  {theme: MyTheme},
  unknown
>({
  name: "[root]",
  namespace: "My App",
  dependencies: [
    RichTextExtension,
    configExtension(ReactExtension, { contentEditable: null }),
  ],
  nodes: [TooltipNode],
  theme: { tooltip: 'MyEditorTheme__tooltip' },
});

// TooltipNode.ts
createDOM(config: EditorConfig, editor: LexicalEditor): HTMLElement {
  const element = document.createElement('span');
  // Theme will also be available in the config argument.
  // A cleaner option is to define a separate extension for a custom theme
  // or define the node theming in the node extension itself.
  const {output} = getExtensionDependencyFromEditor(editor, editorExtension);
  addClassNamesToElement(element, output.theme.tooltip);
  return element;
}

Well, I agree that this is a cleaner way and probably the only correct one if the user needs to avoid namespace conflicts and support multiple config themes. However, in simpler cases, declaration merging seems more appealing way. At least because the theme object is already available in DOM methods. It is also simply a familiar and fairly common way of extending types for such purposes, and it is also recommended by some large projects, for example:

If you are concerned that this approach will become widespread, I can change the PR description so that users do not consider this method to be officially recommended

@etrepum
Copy link
Collaborator

etrepum commented Mar 3, 2026

No, that's not really at all how it would look, but there is an example https://lexical.dev/docs/extensions/migration#react-plug-in-without-ui if you choose the "After (all-in)" tab.

etrepum
etrepum previously approved these changes Mar 3, 2026
Copy link
Collaborator

@etrepum etrepum left a comment

Choose a reason for hiding this comment

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

This change is fine, especially since the TypeScript compiler generally performs better with interfaces, but declaration merging and ambient types are not lexical best practices. I would not encourage the use case proposed in the PR description.

@etrepum etrepum added the extended-tests Run extended e2e tests on a PR label Mar 3, 2026
Copy link
Collaborator

@etrepum etrepum left a comment

Choose a reason for hiding this comment

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

On second thought the changes to the flow types should be reverted. Flow doesn't do declaration merging so there's no reason to make any changes there.

@etrepum etrepum dismissed their stale review March 4, 2026 05:17

Flow changes should be reverted

@etrepum etrepum added this pull request to the merge queue Mar 4, 2026
Merged via the queue into facebook:main with commit 38b0d3c Mar 4, 2026
37 checks passed
@etrepum etrepum mentioned this pull request Mar 19, 2026
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. extended-tests Run extended e2e tests on a PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants