@uiw/react-md-editor
    TypeScript icon, indicating that this package has built-in type declarations

    3.13.0 • Public • Published

    react-md-editor logo

    Downloads jsDelivr CDN npm bundle size Coverage Status
    Build & Deploy Open in unpkg Gitee npm version

    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.

    Quick Start

    npm i @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} />
        </div>
      );
    }

    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]],
            }}
          />
          <MDEditor.Markdown 
            source={value} 
            rehypePlugins={[[rehypeSanitize]]}
          />
        </div>
      );
    }

    Custom Toolbars

    Open in CodeSandbox

    import React from "react";
    import ReactDOM from "react-dom";
    import MDEditor, { commands, ICommand, TextState, TextAreaTextApi } from '@uiw/react-md-editor';
    
    const title3: ICommand = {
      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: TextState, api: TextAreaTextApi) => {
        let modifyText = `### ${state.selectedText}\n`;
        if (!state.selectedText) {
          modifyText = `### `;
        }
        api.replaceSelection(modifyText);
      },
    };
    
    export default function App() {
      const [value, setValue] = React.useState("**Hello world!!!**");
      return (
        <div className="container">
          <MDEditor
            value="Hello Markdown!"
            onChange={(val) => {
              setValue(val!);
            }}
            commands={[
              // Custom Toolbars
              title3,
              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: TextState, api: TextAreaTextApi)  => {
                  console.log('>>>>>>update>>>>>', state)
                },
                buttonProps: { 'aria-label': 'Insert title'}
              }),
            ]}
          />
        </div>
      );
    }

    Preview Markdown

    Open in CodeSandbox

    import React from "react";
    import ReactDOM from "react-dom";
    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 ReactDOM from "react-dom";
    import MDEditor from '@uiw/react-md-editor';
    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() {
      return (
        <MDEditor
          value={mdKaTeX}
          previewOptions={{
            components: {
              code: ({ inline, children = [], className, ...props }) => {
                const txt = children[0] || '';
                if (inline) {
                  if (typeof txt === 'string' && /^\$\$(.*)\$\$/.test(txt)) {
                    const html = katex.renderToString(txt.replace(/^\$\$(.*)\$\$/, '$1'), {
                      throwOnError: false,
                    });
                    return <code dangerouslySetInnerHTML={{ __html: html }} />;
                  }
                  return <code>{txt}</code>;
                }
                if (
                  typeof txt === 'string' &&
                  typeof className === 'string' &&
                  /^language-katex/.test(className.toLocaleLowerCase())
                ) {
                  const html = katex.renderToString(txt, {
                    throwOnError: false,
                  });
                  return <code dangerouslySetInnerHTML={{ __html: html }} />;
                }
                return <code className={String(className)}>{txt}</code>;
              },
            },
          }}
        />
      );
    }

    Markdown text to Image

    Open in CodeSandbox

    import React from "react";
    import MDEditor, { commands, 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("w-md-editor")[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() {
      return (
        <div className="container">
          <MDEditor
            value="**Hello world!!!**"
            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 } from "react";
    import ReactDOM from "react-dom";
    import MDEditor from "@uiw/react-md-editor";
    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 }: any) => {
      const demoid = useRef(`dome${randomid()}`);
      const code = getCode(children);
      const demo = useRef(null);
      useEffect(() => {
        if (demo.current) {
          try {
            const str = mermaid.render(demoid.current, code, () => null, demo.current);
            // @ts-ignore
            demo.current.innerHTML = str;
          } catch (error) {
            // @ts-ignore
            demo.current.innerHTML = error;
          }
        }
      }, [code, demo]);
    
      if (
        typeof code === "string" && typeof className === "string" &&
        /^language-mermaid/.test(className.toLocaleLowerCase())
      ) {
        return (
          <code ref={demo}>
            <code id={demoid.current} style={{ display: "none" }} />
          </code>
        );
      }
      return <code className={String(className)}>{children}</code>;
    };
    
    const getCode = (arr = []) => arr.map((dt) => {
      if (typeof dt === "string") {
        return dt;
      }
      if (dt.props && dt.props.children) {
        return getCode(dt.props.children);
      }
      return false;
    }).filter(Boolean).join("");
    
    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
            }
          }}
        />
      );
    }
    
    ReactDOM.render(<App />, document.getElementById("container"));

    Support Nextjs

    Use examples in nextjs. #52 #224

    Open in CodeSandbox Open in StackBlitz

    npm install next-remove-imports
    npm install @uiw/react-md-editor@v3.6.0
    // next.config.js
    const removeImports = require('next-remove-imports')();
    module.exports = removeImports({});
    import "@uiw/react-md-editor/markdown-editor.css";
    import "@uiw/react-markdown-preview/markdown.css";
    import dynamic from "next/dynamic";
    import { useState } from "react";
    
    const MDEditor = dynamic(
      () => import("@uiw/react-md-editor"),
      { ssr: false }
    );
    
    function HomePage() {
      const [value, setValue] = useState("**Hello world!!!**");
      return (
        <div>
          <MDEditor value={value} onChange={setValue} />
        </div>
      );
    }
    
    export default HomePage;

    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?: number, oldValue?: number, state?: ContextStore): editor height change listener.
    • 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.
    • previewOptions?: ReactMarkdown.ReactMarkdownProps: This is reset @uiw/react-markdown-preview settings.
    • textareaProps?: TextareaHTMLAttributes: Set the textarea related props.
    • renderTextarea?: (props, opts) => JSX.Element;: Use div to replace TextArea or re-render TextArea. #193
    • height?: number=200: The height of the editor.
    • visiableDragbar?: 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 visiableDragbar=true value is valid.
    • minHeights?: number=100: Minimum drag height. The visiableDragbar=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

    npm run watch     # Listen create type and .tsx files.
    npm run css:watch # listen to the component compile and output the .css file
    npm run start  # Preview code example.

    Related

    Contributors

    As always, thanks to our amazing contributors!

    Made with github-action-contributors.

    License

    Licensed under the MIT License.

    Install

    npm i @uiw/react-md-editor

    DownloadsWeekly Downloads

    17,601

    Version

    3.13.0

    License

    MIT

    Unpacked Size

    3.92 MB

    Total Files

    279

    Last publish

    Collaborators

    • wcjiang