Skip to content

Conversation

@masenf
Copy link
Collaborator

@masenf masenf commented Jan 14, 2026

  • Bump to react-markdown 10.1.0, bump all plugins to latest versions
  • component_map: "codemap" with "pre" for simpler handling of inline vs code block
  • Add new rx.markdown.plugin function to allow easily loading remark and rehype plugins
  • Add new MarkdownWrapper since the new react-markdown doesn't accept css/className
  • Add use_math, use_gfm, use_raw and friends to customize default plugins

BREAKING: children for textual components is now a bare string instead of a list of str

This depends on a related change in reflex-web, which will include some doc update on how to use the new rx.markdown.plugins interface

Sample Code

Details
import reflex as rx

MARKDOWN_CONTENT = """
# Code

```python
def hello_world():
    print("Hello, world!")
```

Or use some `inline` code.

```javascript
import {h} from 'hastscript'
import {visit} from 'unist-util-visit'

// This plugin is an example to turn `::note` into divs,
// passing arbitrary attributes.
function myRemarkPlugin() {
  /**
   * @param {Root} tree
   *   Tree.
   * @returns {undefined}
   *   Nothing.
   */
  return (tree) => {
    visit(tree, (node) => {
      if (
        node.type === 'containerDirective' ||
        node.type === 'leafDirective' ||
        node.type === 'textDirective'
      ) {
        if (node.name !== 'note') return

        const data = node.data || (node.data = {})
        const tagName = node.type === 'textDirective' ? 'span' : 'div'

        data.hName = tagName
        data.hProperties = h(tagName, node.attributes || {}).properties
      }
    })
  }
}
```

```
No language specified, just a pre block.
```

# Math

This is money: something like the pricing tiers are $100, $1K and $10K

# HTML

<b>This is HTML</b>

# GFM

## Autolink literals

www.example.com, https://example.com, and contact@example.com.

## Footnote

A note[^1]

[^1]: Big note.

## Strikethrough

~one~ or ~~two~~ tildes.

## Table

| a | b  |  c |  d  |
| - | :- | -: | :-: |

## Tasklist

* [ ] to do
* [x] done

# remark-github

## References
*   Commit: f8083175fe890cbf14f41d0a06e7aa35d4989587
*   Commit (fork): foo@f8083175fe890cbf14f41d0a06e7aa35d4989587
*   Commit (repo): remarkjs/remark@e1aa9f6c02de18b9459b7d269712bcb50183ce89
*   Issue or PR (`#`): #1
*   Issue or PR (`GH-`): GH-1
*   Issue or PR (fork): foo#1
*   Issue or PR (project): remarkjs/remark#1
*   Mention: @wooorm

## Links
*   Commit: <https://github.com/remarkjs/remark/commit/e1aa9f6c02de18b9459b7d269712bcb50183ce89>
*   Commit comment: <https://github.com/remarkjs/remark/commit/ac63bc3abacf14cf08ca5e2d8f1f8e88a7b9015c#commitcomment-16372693>
*   Issue or PR: <https://github.com/remarkjs/remark/issues/182>
*   Issue or PR comment: <https://github.com/remarkjs/remark-github/issues/3#issue-151160339>
*   Mention: <https://github.com/ben-eb>
"""


class State(rx.State):
    use_math: bool = True
    use_gfm: bool = True
    use_unwrap_images: bool = True
    use_katex: bool = True
    use_raw: bool = True
    use_github: bool = True


def custom_codeblock(value, **props):
    return rx.box(
        rx.text("Custom Codeblock:"),
        rx._x.code_block(value, **props),
        # rx.el.pre(value),
        rx.text(f"Language: {rx.cond(language := props.get('language'), language, 'N/A')}"),
        padding="10px",
        border="2px solid blue",
        margin_y="10px",
    )


def index() -> rx.Component:
    return rx.container(
        rx.color_mode.button(position="top-right"),
        rx.hstack(
            rx.box(
                rx.markdown(
                    MARKDOWN_CONTENT,
                    use_math=State.use_math,
                    use_gfm=State.use_gfm,
                    use_unwrap_images=State.use_unwrap_images,
                    use_katex=State.use_katex,
                    use_raw=State.use_raw,
                    remark_plugins=rx.cond(
                        State.use_github,
                        [
                            (
                                rx.markdown.plugin("remark-github@12.0.0", "remarkGithub"),
                                {"repository": "remarkjs/remark"},
                            ),
                        ],
                        [],
                    ),
                    # component_map={
                    #     "pre": custom_codeblock,
                    # },
                ),
                overflow_x="auto",
            ),
            rx.spacer(),
            rx.vstack(
                rx.hstack(
                    rx.text("Use Math"),
                    rx.switch(
                        checked=State.use_math,
                        on_change=State.set_use_math,
                    ),
                ),
                rx.hstack(
                    rx.text("Use GFM"),
                    rx.switch(
                        checked=State.use_gfm,
                        on_change=State.set_use_gfm,
                    ),
                ),
                rx.hstack(
                    rx.text("Use Unwrap Images"),
                    rx.switch(
                        checked=State.use_unwrap_images,
                        on_change=State.set_use_unwrap_images,
                    ),
                ),
                rx.hstack(
                    rx.text("Use KaTeX"),
                    rx.switch(
                        checked=State.use_katex,
                        on_change=State.set_use_katex,
                    ),
                ),
                rx.hstack(
                    rx.text("Use Raw"),
                    rx.switch(
                        checked=State.use_raw,
                        on_change=State.set_use_raw,
                    ),
                ),
                rx.hstack(
                    rx.text("Use GitHub"),
                    rx.switch(
                        checked=State.use_github,
                        on_change=State.set_use_github,
                    ),
                ),
                top="25px",
                position="sticky",
                align="end",
                min_width="200px",
            ),
        ),
        rx.logo(),
    )


app = rx.App()
app.add_page(index)

* Bump to react-markdown 10.0.0, bump all plugins to latest versions
* component_map: "codemap" with "pre" for simpler handling of inline vs code block
* Add new `rx.markdown.plugin` function to allow easily loading remark and rehype plugins
* Add new `MarkdownWrapper` since the new react-markdown doesn't accept css/className
* Add `use_math`, `use_gfm`, `use_raw` and friends to customize default plugins

BREAKING: children for textual components is now a bare string instead of a list of str
@codspeed-hq
Copy link

codspeed-hq bot commented Jan 14, 2026

Merging this PR will not alter performance

✅ 8 untouched benchmarks


Comparing masenf/markdown-tlc (178c3b2) with main (726711e)

Open in CodSpeed

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 14, 2026

Greptile Summary

Upgraded react-markdown from 8.0.7 to 10.1.0 and modernized the markdown component architecture with several improvements:

  • Introduced Plugin class with rx.markdown.plugin() API for easier remark/rehype plugin loading
  • Added MarkdownWrapper component with use_math, use_gfm, use_katex, use_raw, use_unwrap_images boolean flags to control default plugins
  • Renamed "codeblock" component map key to "pre" for more accurate HTML semantics (with deprecation warning)
  • Refactored code block handling to extract language from rest.children.props instead of inline conditional logic
  • Removed NO_PROPS_TAGS special handling - now all components receive props consistently
  • Simplified component map logic by removing dual inline code/codeblock handling

Breaking change: Children for textual components are now bare strings instead of lists of strings

Confidence Score: 4/5

  • Safe to merge with minor concerns about the new code block prop extraction pattern
  • This is a well-tested refactor with comprehensive test updates. The changes simplify the codebase and upgrade to the latest react-markdown version. The deprecation warning for codeblock provides a migration path. Score reduced to 4 due to the new rest.children.props extraction pattern which is less explicit than the previous approach and could be fragile if react-markdown's internal structure changes
  • Pay close attention to reflex/components/markdown/markdown.py - the new code block handling pattern extracts props from rest.children.props which depends on react-markdown 10's internal structure

Important Files Changed

Filename Overview
tests/units/components/markdown/test_markdown.py Updated test assertions to reflect new codeblock handling with pre tag and new function signature
reflex/components/markdown/markdown.py Major refactor: upgraded react-markdown to 10.1.0, added Plugin system, renamed codeblock to pre, introduced MarkdownWrapper, removed NO_PROPS_TAGS logic

Sequence Diagram

sequenceDiagram
    participant User
    participant MarkdownWrapper
    participant Markdown
    participant Plugin
    participant ReactMarkdown

    User->>MarkdownWrapper: create(content, use_math=True, use_gfm=True)
    MarkdownWrapper->>Plugin: Access plugin.math, plugin.gfm
    Plugin-->>MarkdownWrapper: Return plugin Vars with imports
    MarkdownWrapper->>MarkdownWrapper: Assemble remark_plugins array
    MarkdownWrapper->>MarkdownWrapper: Assemble rehype_plugins array
    MarkdownWrapper->>Markdown: create(content, remark_plugins, rehype_plugins, component_map)
    Markdown->>Markdown: Process component_map (handle codeblock deprecation)
    Markdown->>Markdown: Generate component map hash
    Markdown->>Markdown: Create pre handler (extract from rest.children.props)
    Markdown->>ReactMarkdown: Render with components and plugins
    ReactMarkdown-->>User: Rendered markdown
Loading

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Additional Comments (2)

  1. tests/units/components/markdown/test_markdown.py, line 147-149 (link)

    logic: Test expects result["code"] but new implementation moved code block handling to result["pre"] (line 367 in markdown.py). Tests will fail - should test "pre" key instead.

  2. tests/units/components/markdown/test_markdown.py, line 154-156 (link)

    style: Using deprecated "codeblock" in component_map - should be "pre" per the deprecation warning in markdown.py:304-311

4 files reviewed, 5 comments

Edit Code Review Agent Settings | Greptile

@masenf
Copy link
Collaborator Author

masenf commented Jan 15, 2026

@greptile-apps please re-review

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request modernizes the Reflex markdown component by upgrading to react-markdown 10.1.0 and implementing a more flexible plugin architecture. The key changes include replacing "codeblock" with "pre" for code block handling, introducing a new Plugin API for easily loading remark/rehype plugins, and adding the MarkdownWrapper component with optional built-in plugins that can be toggled via props.

Changes:

  • Upgraded react-markdown from 8.0.7 to 10.1.0 and all related plugin packages
  • Changed component_map key from "codeblock" to "pre" for simpler inline vs block code handling
  • Added new Plugin class and MarkdownNamespace with plugin accessor for dynamic plugin loading
  • Added MarkdownWrapper with use_math, use_gfm, use_katex, use_raw, and use_unwrap_images props

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
reflex/components/markdown/markdown.py Upgraded library, added Plugin class, MarkdownWrapper, and MarkdownNamespace; refactored code block handling from "codeblock" to "pre"
reflex/components/markdown/init.py Updated export to use new markdown namespace
tests/units/components/markdown/test_markdown.py Updated test expectations for "pre" mapping instead of "codeblock"
tests/units/components/typography/test_markdown.py Updated import path to reflect new module structure
.github/workflows/check_outdated_dependencies.yml Removed markdown plugin packages from outdated dependency ignore list
pyi_hashes.json Updated hash for markdown.pyi

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@adhami3310 adhami3310 merged commit 2545378 into main Jan 15, 2026
54 checks passed
@adhami3310 adhami3310 deleted the masenf/markdown-tlc branch January 15, 2026 20:14
adhami3310 pushed a commit to reflex-dev/reflex-web that referenced this pull request Jan 15, 2026
* Updates for react-markdown 10.0.0 and associated reflex changes

* Update markdown docs with info on plugins

* fix misrendered nested code blocks in markdown examples
* update deps

* update pyproject.toml

* Robust slugify of markdown header data

You can now include nested markdown in headers and it will slugify it correctly
using hast-util-to-string to properly extract recursively nested elements.

Also includes a driveby fix for scroll-margin when the hosting banner is
displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants