react-md-editor

uiwjsMIT活跃维护

基于 React 和 TypeScript 的 Markdown 编辑器组件,带实时预览,可直接嵌入前端项目,省去自己搭编辑和渲染的功夫。

A simple markdown editor with preview, implemented with React.js and TypeScript.

v4.2.3发布于 2026-11-03 15:10 UTC2,936star200fork同步于 2026-11-10 05:41 UTC

怎么上手

从 README 代码块原样提取,可能是其中一种安装方式
npm i @uiw/react-md-editor
yarn add @uiw/react-md-editor
硬件要求按 README 关键词推断
支持 Apple Silicon
下载:源码 zip·GitHub Releases(安装包)本站不镜像安装包

项目说明

这个项目的说明还在生成中,先参考下面「最近发版」和 README。

最近发版

5 个版本v4.2.3
  1. v4.2.3最新正式版2026-11-03 · 2026-11-03 15:10 UTC

    Buy me a coffee

    Documentation v4.2.3: https://raw.githack.com/uiwjs/react-md-editor/0d7c731/index.html
    Comparing Changes: https://github.com/uiwjs/react-md-editor/compare/v4.2.2...v4.2.3

    npm i @uiw/[email protected]
    
    • ⛑ test: Test/html comment regression 652 (#725) bba4796 @hiyouwhatsapp
    • 💢 ci: run tests on every pull request (#728) 0b373d0 @jaywcjlove
    • 🐞 fix: remount embedded HTML previews after source changes (#726) 74935a6 @hiyouwhatsapp
    • 🐞 fix(editor): disable toolbar and resize controls with disabled prop (#727) c7829ca @hiyouwhatsapp
    • 🐞 fix: Fix/nextjs code highlighting 682 (#729) aade76e @hiyouwhatsapp
  2. v4.2.22026-11-01 · 2026-11-01 14:50 UTC
    查看更新说明

    Buy me a coffee

    Documentation v4.2.2: https://raw.githack.com/uiwjs/react-md-editor/0c39c65/index.html
    Comparing Changes: https://github.com/uiwjs/react-md-editor/compare/v4.2.1...v4.2.2

    npm i @uiw/[email protected]
    
    • 💢 ci: update .github/workflows/package-consumers.yml ed3483a @jaywcjlove
    • 🐞 fix: Fix/preview adaptive height 631 (#724) 58a818f @hiyouwhatsapp
  3. v4.2.12026-11-01 · 2026-11-01 09:27 UTC
    查看更新说明

    Buy me a coffee

    Documentation v4.2.1: https://raw.githack.com/uiwjs/react-md-editor/37e78ac/index.html
    Comparing Changes: https://github.com/uiwjs/react-md-editor/compare/v4.2.0...v4.2.1

    npm i @uiw/[email protected]
    
    • 🐞 fix: Fix/fullscreen accessibility #687 (#723) 13e8c1c @hiyouwhatsapp
  4. v4.2.02026-10-31 · 2026-10-31 00:10 UTC
    查看更新说明

    Buy me a coffee

    Documentation v4.2.0: https://raw.githack.com/uiwjs/react-md-editor/ff2d1e9/index.html
    Comparing Changes: https://github.com/uiwjs/react-md-editor/compare/v4.1.7...v4.2.0

    npm i @uiw/[email protected]
    
    • ⛑ test: keep the package consumer fixture out of Jest cf0d6bb @jaywcjlove
    • 🐞 fix: sync preview scrolling with the editor (#722) 6cabc20 @hiyouwhatsapp
  5. v4.1.72026-10-30 · 2026-10-30 17:52 UTC
    查看更新说明

    Buy me a coffee

    Documentation v4.1.7: https://raw.githack.com/uiwjs/react-md-editor/d907d1e/index.html
    Comparing Changes: https://github.com/uiwjs/react-md-editor/compare/v4.1.6...v4.1.7

    npm i @uiw/[email protected]
    
    • 📖 doc: update core/README.md. ba69f89 @github-actions-bot
    • 🐞 fix: Fix/vite bun exports 696 (#720) 606069f @hiyouwhatsapp
    • 🐞 fix: keep highlighted list and table tokens inline (#718) 83f3cbb @hiyouwhatsapp
    • ⛑ test: fix highlight layout and fixture suite failures (#718) 78163bb @jaywcjlove
    • 🐞 fix: keep large markdown documents responsive (#719) 13d3041 @hiyouwhatsapp
    • 📖 doc: update core/README.md. e1c92c7 @github-actions-bot

用户点评

登录用户的真实使用记录,可按版本和硬件筛选
共 0 条

    还没有人点评。第一个说出真实体验的人最有价值。

    README 原文

    未翻译

    A simple markdown editor with preview, implemented with React.js and TypeScript. This React Component aims to provide a simple Markdown editor with syntax highlighting support. This is based on textarea encapsulation, so it does not depend on any modern code editors such as Acs, CodeMirror, Monaco etc.

    Features

    • 📑 Indent line or selected text by pressing tab key, with customizable indentation.
    • ♻️ Based on textarea encapsulation, does not depend on any modern code editors.
    • 🚧 Does not depend on the uiw component library.
    • 🚘 Automatic list on new lines.
    • 😻 GitHub flavored markdown support.
    • 🌒 Support dark-mode/night-mode @v3.11.0+.
    • 💡 Support next.js, Use examples in next.js.
    • Line/lines duplication (Ctrl+D) and movement (Alt+UpArrow/DownArrow) @v3.24.0+.

    Quick Start

    npm i @uiw/react-md-editor
    

    or

    yarn add @uiw/react-md-editor
    

    Using

    Open in CodeSandbox Open in Github gh-pages Open in Gitee gh-pages

    import React from "react";
    import MDEditor from '@uiw/react-md-editor';
    
    export default function App() {
      const [value, setValue] = React.useState("**Hello world!!!**");
      return (
        <div className="container">
          <MDEditor
            value={value}
            onChange={setValue}
          />
          <MDEditor.Markdown source={value} style={{ whiteSpace: 'pre-wrap' }} />
        </div>
      );
    }
    

    Headless Mode

    The package exposes the necessary utilities to build a headless markdown editor with your own UI. This example creates a simple textarea that supports markdown keyboard shortcuts and correct handling of newlines.

    import React from "react";
    import { 
      handleKeyDown, 
      shortcuts, 
      TextAreaCommandOrchestrator,
      getCommands,
    } from '@uiw/react-md-editor';
    
    export default function App() {
      const [value, setValue] = React.useState("**Hello world!!!**");
      const textareaRef = React.useRef(null);
      const orchestratorRef = React.useRef(null);
      
      React.useEffect(() => {
        if (textareaRef.current) {
          orchestratorRef.current = new TextAreaCommandOrchestrator(textareaRef.current);
        }
      }, []);
    
      const onKeyDown = (e) => {
        handleKeyDown(e, 2, false);
        if (orchestratorRef.current) {
          shortcuts(e, getCommands(), orchestratorRef.current);
        }
      };
    
      return (
        <textarea
          ref={textareaRef}
          value={value}
          onChange={(e) => setValue(e.target.value)}
          onKeyDown={onKeyDown}
          style={{ width: '100%', height: 200, padding: 10 }}
        />
      );
    }
    

    Special Markdown syntax

    Supports for CSS Style

    Use HTML comments <!--rehype:xxx--> to let Markdown support style customization.

    ## Title
    <!--rehype:style=display: flex; height: 230px; align-items: center; justify-content: center; font-size: 38px;-->
    
    Markdown Supports **Style**<!--rehype:style=color: red;-->
    

    Ignore content display via HTML comments

    Shown in GitHub readme, excluded in HTML.

    # Hello World
    
    <!--rehype:ignore:start-->Hello World<!--rehype:ignore:end-->
    
    Good!
    

    Output:

    <h1>Hello World</h1>
    
    <p>Good!</p>
    

    Security

    Please note markdown needs to be sanitized if you do not completely trust your authors. Otherwise, your app is vulnerable to XSS. This can be achieved by adding rehype-sanitize as a plugin.

    import React from "react";
    import MDEditor from '@uiw/react-md-editor';
    import rehypeSanitize from "rehype-sanitize";
    
    export default function App() {
      const [value, setValue] = React.useState(`**Hello world!!!** <IFRAME SRC=\"javascript:javascript:alert(window.origin);\"></IFRAME>`);
      return (
        <div className="container">
          <MDEditor
            value={value}
            onChange={setValue}
            previewOptions={{
              rehypePlugins: [[rehypeSanitize]],
            }}
          />
        </div>
      );
    }
    

    Remove Code Highlight

    The following example can help you exclude code highlighting code from being included in the bundle. @uiw/react-md-editor/nohighlight component does not contain the rehype-prism-plus code highlighting package, highlightEnable, showLineNumbers and highlight line functions will no longer work. (#586)

    import React from "react";
    import MDEditor from '@uiw/react-md-editor/nohighlight';
    
    const code = `**Hello world!!!**
    \`\`\`js
    function demo() {}
    \`\`\`
    `
    
    export default function App() {
      const [value, setValue] = React.useState(code);
      return (
        <div className="container">
          <MDEditor
            value={value}
            onChange={setValue}
          />
          <MDEditor.Markdown source={value} style={{ whiteSpace: 'pre-wrap' }} />
        </div>
      );
    }
    

    Common Code Highlight

    Use @uiw/react-md-editor/common to keep syntax highlighting while only including the rehype-prism-plus/common language subset. This is a middle ground between the default full-language build and nohighlight.

    import React from "react";
    import MDEditor from '@uiw/react-md-editor/common';
    
    const code = `**Hello world!!!**
    \`\`\`js
    function greet(name) {
      console.log('hello', name);
    }
    \`\`\`
    `
    
    export default function App() {
      const [value, setValue] = React.useState(code);
      return (
        <div className="container">
          <MDEditor
            value={value}
            onChange={setValue}
          />
          <MDEditor.Markdown source={value} style={{ whiteSpace: 'pre-wrap' }} />
        </div>
      );
    }
    

    Placeholder & maxLength

    "Below is an example that sets the placeholder for the editor and defines the maximum input character length as 10 characters."

    import React from "react";
    import MDEditor from '@uiw/react-md-editor';
    
    export default function App() {
      const [value, setValue] = React.useState("");
      return (
          <MDEditor
            value={value}
            onChange={setValue}
            textareaProps={{
              placeholder: 'Please enter Markdown text',
              maxLength: 10
            }}
          />
      );
    }
    

    Custom Toolbars

    List commands select a word by default. Custom commands can pass 'line' to makeList to select the entire line instead

    import MDEditor, { commands } from '@uiw/react-md-editor';
    
    const lineListCommand = {
      ...commands.unorderedListCommand,
      execute: (state, api) => commands.makeList(state, api, '- ', 'line'),
    };
    
    // <MDEditor commands={[lineListCommand]} />
    
    

    Open in CodeSandbox

    import React, { useState } from "react";
    import MDEditor, { commands } from '@uiw/react-md-editor';
    
    const title3 = {
      name: 'title3',
      keyCommand: 'title3',
      buttonProps: { 'aria-label': 'Insert title3' },
      icon: (
        <svg width="12" height="12" viewBox="0 0 520 520">
          <path fill="currentColor" d="M15.7083333,468 C7.03242448,468 0,462.030833 0,454.666667 L0,421.333333 C0,413.969167 7.03242448,408 15.7083333,408 L361.291667,408 C369.967576,408 377,413.969167 377,421.333333 L377,454.666667 C377,462.030833 369.967576,468 361.291667,468 L15.7083333,468 Z M21.6666667,366 C9.69989583,366 0,359.831861 0,352.222222 L0,317.777778 C0,310.168139 9.69989583,304 21.6666667,304 L498.333333,304 C510.300104,304 520,310.168139 520,317.777778 L520,352.222222 C520,359.831861 510.300104,366 498.333333,366 L21.6666667,366 Z M136.835938,64 L136.835937,126 L107.25,126 L107.25,251 L40.75,251 L40.75,126 L-5.68434189e-14,126 L-5.68434189e-14,64 L136.835938,64 Z M212,64 L212,251 L161.648438,251 L161.648438,64 L212,64 Z M378,64 L378,126 L343.25,126 L343.25,251 L281.75,251 L281.75,126 L238,126 L238,64 L378,64 Z M449.047619,189.550781 L520,189.550781 L520,251 L405,251 L405,64 L449.047619,64 L449.047619,189.550781 Z" />
        </svg>
      ),
      execute: (state, api) => {
        let modifyText = `### ${state.selectedText}\n`;
        if (!state.selectedText) {
          modifyText = `### `;
        }
        api.replaceSelection(modifyText);
      },
    };
    
    const title2 = {
      name: 'title2',
      keyCommand: 'title2',
      render: (command, disabled, executeCommand) => {
        return (
          <button 
            aria-label="Insert title2"
            disabled={disabled}
            onClick={(evn) => {
              // evn.stopPropagation();
              executeCommand(command, command.groupName)
            }}
          >
            <svg width="12" height="12" viewBox="0 0 520 520">
              <path fill="currentColor" d="M15.7083333,468 C7.03242448,468 0,462.030833 0,454.666667 L0,421.333333 C0,413.969167 7.03242448,408 15.7083333,408 L361.291667,408 C369.967576,408 377,413.969167 377,421.333333 L377,454.666667 C377,462.030833 369.967576,468 361.291667,468 L15.7083333,468 Z M21.6666667,366 C9.69989583,366 0,359.831861 0,352.222222 L0,317.777778 C0,310.168139 9.69989583,304 21.6666667,304 L498.333333,304 C510.300104,304 520,310.168139 520,317.777778 L520,352.222222 C520,359.831861 510.300104,366 498.333333,366 L21.6666667,366 Z M136.835938,64 L136.835937,126 L107.25,126 L107.25,251 L40.75,251 L40.75,126 L-5.68434189e-14,126 L-5.68434189e-14,64 L136.835938,64 Z M212,64 L212,251 L161.648438,251 L161.648438,64 L212,64 Z M378,64 L378,126 L343.25,126 L343.25,251 L281.75,251 L281.75,126 L238,126 L238,64 L378,64 Z M449.047619,189.550781 L520,189.550781 L520,251 L405,251 L405,64 L449.047619,64 L449.047619,189.550781 Z" />
            </svg>
          </button>
        )
      },
      execute: (state, api) => {
        let modifyText = `## ${state.selectedText}\n`;
        if (!state.selectedText) {
          modifyText = `## `;
        }
        api.replaceSelection(modifyText);
      },
    }
    
    function SubChildren({ close, execute, getState, textApi, dispatch }) {
      const [value, setValue] = useState('')
      const insert = () => {
        console.log('value:::', value)
        textApi.replaceSelection(value)
      }
      return (
        <div style={{ width: 120, padding: 10 }}>
          <div>My Custom Toolbar</div>
          <input type="text" onChange={(e) => setValue(e.target.value)} />
          <button
            type="button"
            onClick={() => {
              dispatch({ $value: '~~~~~~' })
              console.log('> execute: >>>>>', getState())
            }}
          >
            State
          </button>
          <button type="button" onClick={insert}>Insert</button>
          <button type="button" onClick={() => close()}>Close</button>
          <button type="button" onClick={() => execute()}>Execute</button>
        </div>
      );
    }
    
    const subChild = {
      name: 'update',
      groupName: 'update',
      icon: (
        <svg viewBox="0 0 1024 1024" width="12" height="12">
          <path fill="currentColor" d="M716.8 921.6a51.2 51.2 0 1 1 0 102.4H307.2a51.2 51.2 0 1 1 0-102.4h409.6zM475.8016 382.1568a51.2 51.2 0 0 1 72.3968 0l144.8448 144.8448a51.2 51.2 0 0 1-72.448 72.3968L563.2 541.952V768a51.2 51.2 0 0 1-45.2096 50.8416L512 819.2a51.2 51.2 0 0 1-51.2-51.2v-226.048l-57.3952 57.4464a51.2 51.2 0 0 1-67.584 4.2496l-4.864-4.2496a51.2 51.2 0 0 1 0-72.3968zM512 0c138.6496 0 253.4912 102.144 277.1456 236.288l10.752 0.3072C924.928 242.688 1024 348.0576 1024 476.5696 1024 608.9728 918.8352 716.8 788.48 716.8a51.2 51.2 0 1 1 0-102.4l8.3968-0.256C866.2016 609.6384 921.6 550.0416 921.6 476.5696c0-76.4416-59.904-137.8816-133.12-137.8816h-97.28v-51.2C691.2 184.9856 610.6624 102.4 512 102.4S332.8 184.9856 332.8 287.488v51.2H235.52c-73.216 0-133.12 61.44-133.12 137.8816C102.4 552.96 162.304 614.4 235.52 614.4l5.9904 0.3584A51.2 51.2 0 0 1 235.52 716.8C105.1648 716.8 0 608.9728 0 476.5696c0-132.1984 104.8064-239.872 234.8544-240.2816C258.5088 102.144 373.3504 0 512 0z" />
        </svg>
      ),
      children: (props) => <SubChildren {...props} />,
      execute: (state, api)  => {
        console.log('>>>>>>update>>>>>', state)
      },
      buttonProps: { 'aria-label': 'Insert title'}
    }
    
    export default function App() {
      const [value, setValue] = React.useState("Hello Markdown! `Tab` key uses default behavior");
      return (
        <div className="container">
          <MDEditor
            value={value}
            onChange={setValue}
            commands={[
              // Custom Toolbars
              title3, title2,
              commands.group([commands.title1, commands.title2, commands.title3, commands.title4, commands.title5, commands.title6], {
                name: 'title',
                groupName: 'title',
                buttonProps: { 'aria-label': 'Insert title'}
              }),
              commands.divider,
              commands.group([], subChild),
            ]}
          />
        </div>
      );
    }
    

    Customize the toolbar with commands and extraCommands props.

    import React from "react";
    import MDEditor, { commands } from '@uiw/react-md-editor';
    
    export default function App() {
      const [value, setValue] = React.useState("Hello Markdown! `Tab` key uses default behavior");
      return (
        <div className="container">
          <MDEditor
            value={value}
            onChange={setValue}
            preview="edit"
            commands={[
              commands.codeEdit, commands.codePreview
            ]}
            extraCommands={[
              commands.group([commands.title1, commands.title2, commands.title3, commands.title4, commands.title5, commands.title6], {
                name: 'title',
                groupName: 'title',
                buttonProps: { 'aria-label': 'Insert title'}
              }),
              commands.divider,
              commands.group([], {
                name: 'update',
                groupName: 'update',
                icon: (
                  <svg viewBox="0 0 1024 1024" width="12" height="12">
                    <path fill="currentColor" d="M716.8 921.6a51.2 51.2 0 1 1 0 102.4H307.2a51.2 51.2 0 1 1 0-102.4h409.6zM475.8016 382.1568a51.2 51.2 0 0 1 72.3968 0l144.8448 144.8448a51.2 51.2 0 0 1-72.448 72.3968L563.2 541.952V768a51.2 51.2 0 0 1-45.2096 50.8416L512 819.2a51.2 51.2 0 0 1-51.2-51.2v-226.048l-57.3952 57.4464a51.2 51.2 0 0 1-67.584 4.2496l-4.864-4.2496a51.2 51.2 0 0 1 0-72.3968zM512 0c138.6496 0 253.4912 102.144 277.1456 236.288l10.752 0.3072C924.928 242.688 1024 348.0576 1024 476.5696 1024 608.9728 918.8352 716.8 788.48 716.8a51.2 51.2 0 1 1 0-102.4l8.3968-0.256C866.2016 609.6384 921.6 550.0416 921.6 476.5696c0-76.4416-59.904-137.8816-133.12-137.8816h-97.28v-51.2C691.2 184.9856 610.6624 102.4 512 102.4S332.8 184.9856 332.8 287.488v51.2H235.52c-73.216 0-133.12 61.44-133.12 137.8816C102.4 552.96 162.304 614.4 235.52 614.4l5.9904 0.3584A51.2 51.2 0 0 1 235.52 716.8C105.1648 716.8 0 608.9728 0 476.5696c0-132.1984 104.8064-239.872 234.8544-240.2816C258.5088 102.144 373.3504 0 512 0z" />
                  </svg>
                ),
                children: ({ close, execute, getState, textApi }) => {
                  return (
                    <div style={{ width: 120, padding: 10 }}>
                      <div>My Custom Toolbar</div>
                      <button type="button" onClick={() => console.log('> execute: >>>>>', getState())}>State</button>
                      <button type="button" onClick={() => close()}>Close</button>
                      <button type="button" onClick={() => execute()}>Execute</button>
                    </div>
                  );
                },
                execute: (state, api)  => {
                  console.log('>>>>>>update>>>>>', state)
                },
                buttonProps: { 'aria-label': 'Insert title'}
              }),
              commands.divider, commands.fullscreen
            ]}
          />
        </div>
      );
    }
    

    re-render toolbar element.

    import React from "react";
    import MDEditor, { commands } from '@uiw/react-md-editor';
    
    export default function App() {
      const [value, setValue] = React.useState("Hello Markdown! `Tab` key uses default behavior");
      return (
        <div className="container">
          <MDEditor
            value={value}
            onChange={setValue}
            preview="edit"
            components={{
              toolbar: (command, disabled, executeCommand) => {
                if (command.keyCommand === 'code') {
                  return (
                    <button 
                      aria-label="Insert code"
                      disabled={disabled}
                      onClick={(evn) => {
                        evn.stopPropagation();
                        executeCommand(command, command.groupName)
                      }}
                    >
                      Code
                    </button>
                  )
                }
              }
            }}
          />
        </div>
      );
    }
    

    Custom Preview Command Tool

    Open in CodeSandbox

    import React, { useContext } from "react";
    import MDEditor, { commands, EditorContext } from "@uiw/react-md-editor";
    
    const Button = () => {
      const { preview, dispatch } = useContext(EditorContext);
      const click = () => {
        dispatch({
          preview: preview === "edit" ? "preview" : "edit"
        });
      };
      if (preview === "edit") {
        return (
          <svg width="12" height="12" viewBox="0 0 520 520" onClick={click}>
            <polygon
              fill="currentColor"
              points="0 71.293 0 122 319 122 319 397 0 397 0 449.707 372 449.413 372 71.293"
            />
            <polygon
              fill="currentColor"
              points="429 71.293 520 71.293 520 122 481 123 481 396 520 396 520 449.707 429 449.413"
            />
          </svg>
        );
      }
      return (
        <svg width="12" height="12" viewBox="0 0 520 520" onClick={click}>
          <polygon
            fill="currentColor"
            points="0 71.293 0 122 38.023 123 38.023 398 0 397 0 449.707 91.023 450.413 91.023 72.293"
          />
          <polygon
            fill="currentColor"
            points="148.023 72.293 520 71.293 520 122 200.023 124 200.023 397 520 396 520 449.707 148.023 450.413"
          />
        </svg>
      );
    };
    
    const codePreview = {
      name: "preview",
      keyCommand: "preview",
      value: "preview",
      icon: <Button />
    };
    
    const Disable = () => {
      const { preview, dispatch } = useContext(EditorContext);
      return (
        <button disabled={preview === "preview"}>
          <svg viewBox="0 0 16 16" width="12px" height="12px">
            <path
              d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8Zm.9 13H7v-1.8h1.9V13Zm-.1-3.6v.5H7.1v-.6c.2-2.1 2-1.9 1.9-3.2.1-.7-.3-1.1-1-1.1-.8 0-1.2.7-1.2 1.6H5c0-1.7 1.2-3 2.9-3 2.3 0 3 1.4 3 2.3.1 2.3-1.9 2-2.1 3.5Z"
              fill="currentColor"
            />
          </svg>
        </button>
      )
    }
    
    const customButton = {
      name: "disable",
      keyCommand: "disable",
      value: "disable",
      icon: <Disable />
    }
    
    export default function App() {
      const [value, setValue] = React.useState("**Hello world!!!**");
      return (
        <div className="container">
          <div>The system automatically sets the theme</div>
          <MDEditor
            value={value}
            preview="edit"
            extraCommands={[codePreview, customButton, commands.fullscreen]}
            onChange={(val) => setValue(val)}
          />
        </div>
      );
    }
    

    Add Help Command Tool

    Open in CodeSandbox

    import React, { useContext } from "react";
    import MDEditor, { commands } from "@uiw/react-md-editor";
    
    const help = {
      name: "help",
      keyCommand: "help",
      buttonProps: { "aria-label": "Insert help" },
      icon: (
        <svg viewBox="0 0 16 16" width="12px" height="12px">
          <path
            d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8Zm.9 13H7v-1.8h1.9V13Zm-.1-3.6v.5H7.1v-.6c.2-2.1 2-1.9 1.9-3.2.1-.7-.3-1.1-1-1.1-.8 0-1.2.7-1.2 1.6H5c0-1.7 1.2-3 2.9-3 2.3 0 3 1.4 3 2.3.1 2.3-1.9 2-2.1 3.5Z"
            fill="currentColor"
          />
        </svg>
      ),
      execute: (state, api) => {
        window.open("https://www.markdownguide.org/basic-syntax/", "_blank");
      }
    };
    
    export default function App() {
      const [value, setValue] = React.useState("**Hello world!!!**");
      return (
        <MDEditor
          value={value}
          preview="edit"
          commands={[...commands.getCommands(), help]}
          onChange={(val) => setValue(val)}
        />
      );
    }
    

    Internationalization Example, You can refer to commands-cn for internationalization.

    import React, { useContext } from "react";
    import MDEditor, { commands } from "@uiw/react-md-editor";
    import { getCommands, getExtraCommands } from "@uiw/react-md-editor/commands-cn";
    
    export default function App() {
      const [value, setValue] = React.useState("**Hello world!!!**");
      return (
        <MDEditor
          value={value}
          preview="edit"
          commands={[...getCommands()]}
          extraCommands={[...getExtraCommands()]}
          onChange={(val) => setValue(val)}
        />
      );
    }
    

    Set to support editor only

    import React, { useContext } from "react";
    import MDEditor, { commands, EditorContext } from "@uiw/react-md-editor";
    
    export default function App() {
      const [value, setValue] = React.useState("**Hello world!!!**");
      return (
        <div className="container">
          <MDEditor
            value={value}
            preview="edit"
            extraCommands={[commands.fullscreen]}
            onChange={(val) => setValue(val)}
          />
        </div>
      );
    }
    

    Editor Font Size

    Open in CodeSandbox #425

    body .w-md-editor-text-pre > code,
    body .w-md-editor-text-input {
      font-size: 23px !important;
      line-height: 24px !important;
    }
    

    Editor height adapts to text

    The initial height can be adjusted through minHeight={100}. Dragbar will automatically expire. You can hide the drag button through visibleDragbar={false}

    import React from "react";
    import MDEditor from '@uiw/react-md-editor';
    
    export default function App() {
      const [value, setValue] = React.useState("**Hello world!!!**");
      return (
        <div className="container">
          <MDEditor
            value={value}
            height="100%"
            // minHeight={50}
            visibleDragbar={false}
            onChange={setValue}
          />
        </div>
      );
    }
    

    Disallowed Elements

    import React from "react";
    import MDEditor from '@uiw/react-md-editor';
    
    export default function App() {
      const [value, setValue] = React.useState("**Hello world!!!**  <style>body{display:none;}</style> ");
      return (
        <div className="container">
          <MDEditor
            value={value}
            height="100%"
            previewOptions={{
              disallowedElements: ['style'],
            }}
            visibleDragbar={false}
            onChange={setValue}
          />
        </div>
      );
    }
    

    Place the cursor at the end of the text

    import React from "react";
    import MDEditor from '@uiw/react-md-editor';
    
    const textSample = `# Welcome to the Markdown Editor!
    
    This is a sample of the **React Markdown Editor**.
    
    ## ✨ Features
    - Real-time preview
    - Custom styling support
    - Code highlighting
    - Auto focus at the end of the text
    
    ## 📦 Sample Code
    
    \`\`\`javascript
    function hello() {
      console.log("Hello, world!");
    }
    \`\`\`
    
    ## 🔗 Links
    
    Visit [uiwjs/react-md-editor](https://github.com/uiwjs/react-md-editor) for more information.
    `;
    
    export default function App() {
      const [value, setValue] = React.useState(textSample);
      return (
        <div className="container">
          <MDEditor
            autoFocus={true}
            value={value}
            autoFocusEnd={true}
            visibleDragbar={false}
            onChange={setValue}
          />
        </div>
      );
    }
    

    Preview Markdown

    Open in CodeSandbox

    import React from "react";
    import MDEditor from '@uiw/react-md-editor';
    
    export default function App() {
      return (
        <div className="container">
          <MDEditor.Markdown source="Hello Markdown!" />
        </div>
      );
    }
    

    Support Custom KaTeX Preview

    KaTeX is a fast, easy-to-use JavaScript library for TeX math rendering on the web, We perform math rendering through KaTeX.

    The following example is preview in CodeSandbox.

    Open in CodeSandbox

    ⚠️ Upgrade v2 to v3 d025430

    npm install katex
    
    import React from "react";
    import MDEditor from '@uiw/react-md-editor';
    import { getCodeString } from 'rehype-rewrite';
    import katex from 'katex';
    import 'katex/dist/katex.css';
    
    const mdKaTeX = `This is to display the 
    \`\$\$\c = \\pm\\sqrt{a^2 + b^2}\$\$\`
     in one line
    
    \`\`\`KaTeX
    c = \\pm\\sqrt{a^2 + b^2}
    \`\`\`
    `;
    
    export default function App() {
      const [value, setValue] = React.useState(mdKaTeX);
      return (
        <MDEditor
          value={value}
          onChange={(val) => setValue(val)}
          previewOptions={{
            components: {
              code: ({ children = [], className, ...props }) => {
                if (typeof children === 'string' && /^\$\$(.*)\$\$/.test(children)) {
                  const html = katex.renderToString(children.replace(/^\$\$(.*)\$\$/, '$1'), {
                    throwOnError: false,
                  });
                  return <code dangerouslySetInnerHTML={{ __html: html }} style={{ background: 'transparent' }} />;
                }
                const code = props.node && props.node.children ? getCodeString(props.node.children) : children;
                if (
                  typeof code === 'string' &&
                  typeof className === 'string' &&
                  /^language-katex/.test(className.toLocaleLowerCase())
                ) {
                  const html = katex.renderToString(code, {
                    throwOnError: false,
                  });
                  return <code style={{ fontSize: '150%' }} dangerouslySetInnerHTML={{ __html: html }} />;
                }
                return <code className={String(className)}>{children}</code>;
              },
            },
          }}
        />
      );
    }
    

    Markdown text to Image

    Open in CodeSandbox

    import React, { useState } from "react";
    import MDEditor, { commands, type ICommand, TextState, TextAreaTextApi } from "@uiw/react-md-editor";
    import domToImage from "dom-to-image";
    
    const textToImage: ICommand = {
      name: "Text To Image",
      keyCommand: "text2image",
      buttonProps: { "aria-label": "Insert title3" },
      icon: (
        <svg width="12" height="12" viewBox="0 0 20 20">
          <path fill="currentColor" d="M15 9c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm4-7H1c-.55 0-1 .45-1 1v14c0 .55.45 1 1 1h18c.55 0 1-.45 1-1V3c0-.55-.45-1-1-1zm-1 13l-6-5-2 2-4-5-4 8V4h16v11z" ></path>
        </svg>
      ),
      execute: (state: TextState, api: TextAreaTextApi) => {
        const dom = document.getElementsByClassName("gooooooooo")[0];
        if (dom) {
          domToImage.toJpeg(dom, {}).then((dataUrl) => {
            const link = document.createElement("a");
            link.download = "image.jpg";
            link.href = dataUrl;
            link.click();
          });
        }
      }
    };
    
    export default function App() {
      const [value, setValue] = useState('**Hello world!!!**');
      console.log('value::', value)
      return (
        <div className="container">
          <MDEditor
            className="gooooooooo"
            onChange={(newValue = "") => setValue(newValue)}
            value={value}
            commands={[
              textToImage,
              commands.divider
            ]}
          />
        </div>
      );
    }
    

    Support Custom Mermaid Preview

    Using mermaid to generation of diagram and flowchart from text in a similar manner as markdown

    Open in CodeSandbox

    npm install mermaid
    
    import React, { useState, useRef, useEffect, Fragment, useCallback } from "react";
    import MDEditor from "@uiw/react-md-editor";
    import { getCodeString } from 'rehype-rewrite';
    import mermaid from "mermaid";
    
    const mdMermaid = `The following are some examples of the diagrams, charts and graphs that can be made using Mermaid and the Markdown-inspired text specific to it. 
    
    \`\`\`mermaid
    graph TD
    A[Hard] -->|Text| B(Round)
    B --> C{Decision}
    C -->|One| D[Result 1]
    C -->|Two| E[Result 2]
    \`\`\`
    
    \`\`\`mermaid
    sequenceDiagram
    Alice->>John: Hello John, how are you?
    loop Healthcheck
        John->>John: Fight against hypochondria
    end
    Note right of John: Rational thoughts!
    John-->>Alice: Great!
    John->>Bob: How about you?
    Bob-->>John: Jolly good!
    \`\`\`
    `;
    
    const randomid = () => parseInt(String(Math.random() * 1e15), 10).toString(36);
    const Code = ({ inline, children = [], className, ...props }) => {
      const demoid = useRef(`dome${randomid()}`);
      const [container, setContainer] = useState(null);
      const isMermaid =
        className && /^language-mermaid/.test(className.toLocaleLowerCase());
      const code = children
        ? getCodeString(props.node.children)
        : children[0] || "";
    
      useEffect(() => {
        if (container && isMermaid && demoid.current && code) {
          mermaid
            .render(demoid.current, code)
            .then(({ svg, bindFunctions }) => {
              container.innerHTML = svg;
              if (bindFunctions) {
                bindFunctions(container);
              }
            })
            .catch((error) => {
              console.log("error:", error);
            });
        }
      }, [container, isMermaid, code, demoid]);
    
      const refElement = useCallback((node) => {
        if (node !== null) {
          setContainer(node);
        }
      }, []);
    
      if (isMermaid) {
        return (
          <Fragment>
            <code id={demoid.current} style={{ display: "none" }} />
            <code className={className} ref={refElement} data-name="mermaid" />
          </Fragment>
        );
      }
      return <code className={className}>{children}</code>;
    };
    
    export default function App() {
      const [value, setValue] = useState(mdMermaid);
      return (
        <MDEditor
          onChange={(newValue = "") => setValue(newValue)}
          textareaProps={{
            placeholder: "Please enter Markdown text"
          }}
          height={500}
          value={value}
          previewOptions={{
            components: {
              code: Code
            }
          }}
        />
      );
    }
    

    Support Nextjs

    For Next.js App Router, see the working Next.js example (#682)

    Install both packages so their published styles can be imported directly:

    npm install @uiw/react-md-editor @uiw/react-markdown-preview
    

    Load global styles from app/layout.tsx:

    import '@uiw/react-md-editor/markdown-editor.css';
    import '@uiw/react-markdown-preview/markdown.css';
    

    In a client component, load the editor without server-side rendering:

    'use client';
    import dynamic from 'next/dynamic';
    import { useState } from 'react';
    
    const MDEditor = dynamic(() => import('@uiw/react-md-editor'), { ssr: false });
    
    export default function Page() {
      const [value, setValue] = useState(['```js', 'const answer = 42;', '```'].join('\n'));
      return <MDEditor value={value} onChange={setValue} />;
    }
    

    If highlighting is missing with older Turbopack versions, try next dev without --turbopack

    Support dark-mode/night-mode

    By default, the dark-mode is automatically switched according to the system. If you need to switch manually, just set the data-color-mode="dark" parameter for body.

    <html data-color-mode="dark">
    
    document.documentElement.setAttribute('data-color-mode', 'dark')
    document.documentElement.setAttribute('data-color-mode', 'light')
    

    Inherit custom color variables by adding .wmde-markdown-var selector. Setting theme styles with data-color-mode="light".

    <div data-color-mode="light">
      <div className="wmde-markdown-var"> </div>
      <MDEditor source="Hello World!" />
    </div>
    

    Props

    • value: string: The Markdown value.
    • onChange?: (value?: string, event?: React.ChangeEvent<HTMLTextAreaElement>, state?: ContextStore): Event handler for the onChange event.
    • onHeightChange?: ((value?: CSSProperties['height'], oldValue?: CSSProperties['height'], state?: ContextStore): editor height change listener.
    • onStatistics?: (data: Statistics) => void; Some data on the statistics editor.
    • commands?: ICommand[]: An array of ICommand, which, each one, contain a commands property. If no commands are specified, the default will be used. Commands are explained in more details below.
    • commandsFilter?: (command: ICommand, isExtra: boolean) => false | ICommand: Filter or modify your commands.
    • extraCommands?: ICommand[]: Displayed on the right side of the toolbar.
    • autoFocus?: true: Can be used to make Markdown Editor focus itself on initialization.
    • autoFocusEnd?: false: Can be used to make Markdown Editor focus on the end of text on initialization.
    • previewOptions?: ReactMarkdown.ReactMarkdownProps: This is reset @uiw/react-markdown-preview settings.
    • disabled?: boolean=false: Disable the textarea, toolbar commands, fullscreen toggle and resize handle together. Reactively updating this prop re-enables those controls.
    • textareaProps?: TextareaHTMLAttributes: Set the textarea related props.
    • renderTextarea?: (props, opts) => JSX.Element;: @deprecated Please use renderTextarea -> components. Use div to replace TextArea or re-render TextArea. #193
    • components: re-render textarea/toolbar element. #419
      • textarea Use div to replace TextArea or re-render TextArea
      • toolbar Override the default command element. toolbar < command[].render
      • preview Custom markdown preview. #429
    • height?: number=200: The height of the editor. ️⚠️ Dragbar is invalid when height parameter percentage.
    • visibleDragbar?: boolean=true: Show drag and drop tool. Set the height of the editor.
    • highlightEnable?: boolean=true: Disable editing area code highlighting. The value is false, which increases the editing speed.
    • fullscreen?: boolean=false: Show markdown preview.
    • overflow?: boolean=true: Disable fullscreen setting body styles
    • preview?: 'live' | 'edit' | 'preview': Default value live, Show markdown preview.
    • maxHeight?: number=1200: Maximum drag height. The visibleDragbar=true value is valid.
    • minHeight?: number=100: Minimum drag height. The visibleDragbar=true value is valid.
    • tabSize?: number=2: The number of characters to insert when pressing tab key. Default 2 spaces.
    • defaultTabEnable?: boolean=false: If false, the tab key inserts a tab character into the textarea. If true, the tab key executes default behavior e.g. focus shifts to next element.
    • hideToolbar?: boolean=false: Option to hide the tool bar.
    • enableScroll?: boolean=true: Whether to enable scrolling.

    Development

    1. Install dependencies
    $ npm install       # Installation dependencies
    $ npm run build     # Compile all package
    
    1. Development @uiw/react-md-editor package:
    $ cd core
    # listen to the component compile and output the .js file
    # listen for compilation output type .d.ts file
    $ npm run watch # Monitor the compiled package `@uiw/react-md-editor`
    
    1. Launch documentation site
    npm run start
    

    Related

    Contributors

    As always, thanks to our amazing contributors!

    Made with contributors.

    License

    Licensed under the MIT License.