Skip to content

Bug: Markdown exporter trailing spaces inside formatted nodes break CommonMark "flanking" rules #8157

@lbirts

Description

@lbirts

Lexical version: 0.40.0

Steps To Reproduce

  1. Open a Lexical editor with the Markdown plugin enabled.
  2. Type a word, apply Bold formatting, then type a space (while still in bold mode).
  3. Type a second word and apply Italic formatting to it (while still in bold mode).
  4. Type a space and toggle Bold off, then continue typing in Italic only.
  5. Export the content using $convertToMarkdownString.

Link to code example: https://stackblitz.com/edit/github-afacmpsw also reproducible on https://playground.lexical.dev/

Note: Using the stackblitz will show you a preview using CommonMark markdown

Actual vs. Expected Comparison

User Input Pattern Current Output (Broken) Expected Output (Compliant) Parser Result
bold + space **bold **unbold bold unbold Renders as literal **
bold → italic **bold **italic bold italic Fails to close Bold properly
bold → boldeditalic → italic **bold *boldeditalic ***italic bold boldeditalic italic Fails to close Bold and Bolded Italic properly

The current behavior

The Markdown exporter attempts to "optimize" delimiters and capture whitespace inside the formatting nodes. This results in:

Space Capturing: Spaces are escaped as   inside delimiters (e.g., **text **).

Illegal Overlapping: Delimiters are stacked in a way that violates the "nesting" rules of Markdown when formats are toggled at different intervals, resulting in output like:
**bold *boldeditalic ***italic

Because the closing delimiters (_**) are preceded by a space, they fail the Right-Flanking delimiter run requirement of the CommonMark spec. Consequently, most Markdown parsers (like react-markdown or GitHub) render the closing markers as literal text rather than formatting.

The expected behavior

The exporter should prioritize "Hugging" (placing delimiters immediately adjacent to non-whitespace characters) over "Optimization" (keeping tags open across nodes).

Spaces should be "hoisted" outside of formatting delimiters: **bold** ***boldeditalic*** *italic*.

Every text node should ideally close and reopen its own delimiters if it contains trailing whitespace, ensuring all markers satisfy flanking rules.

Impact of fix

Severity: High for apps using Lexical as a headless CMS or Chat editor where the recipient uses a standard Markdown renderer.
Frequency: Happens every time a user types a space before toggling a format off—a very common typing pattern.
Benefit: Anyone using Lexical to generate Markdown for external consumption. Currently, Lexical produces "valid Lexical Markdown" that is "invalid CommonMark," creating a significant break in the ecosystem's interoperability.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions