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.
- 📑 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+.
npm i @uiw/react-md-editor
or
yarn add @uiw/react-md-editor
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>
);
}
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>
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>
);
}
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 code highlighting package, rehype-prism-plus
, highlightEnable
and showLineNumbers
functions will no longer work. (#586)highlight line
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>
);
}
"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
}}
/>
);
}
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
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
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)}
/>
);
}
body .w-md-editor-text-pre > code,
body .w-md-editor-text-input {
font-size: 23px !important;
line-height: 24px !important;
}
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>
);
}
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>
);
}
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>
);
}
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.
⚠️ 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>;
},
},
}}
/>
);
}
import React, { useState } 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("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>
);
}
Using mermaid to generation of diagram and flowchart from text in a similar manner as markdown
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
}
}}
/>
);
}
Use examples in nextjs. #52
#224
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";
import * as commands from "@uiw/react-md-editor/commands"
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;
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>
value: string
: The Markdown value.onChange?: (value?: string, event?: React.ChangeEvent<HTMLTextAreaElement>, state?: ContextStore)
: Event handler for theonChange
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 ofICommand
, which, each one, contain acommands
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 makeMarkdown Editor
focus itself on initialization.previewOptions?: ReactMarkdown.ReactMarkdownProps
: This is reset @uiw/react-markdown-preview settings.textareaProps?: TextareaHTMLAttributes
: Set thetextarea
related props.:renderTextarea?: (props, opts) => JSX.Element;
@deprecated
Please use->renderTextarea
components
. Use div to replace TextArea or re-render TextArea. #193components
: re-render textarea/toolbar element. #419textarea
Use div to replace TextArea or re-render TextAreatoolbar
Override the default command element.toolbar
<command[].render
preview
Custom markdown preview. #429
height?: number=200
: The height of the editor. ️⚠️ Dragbar
is invalid whenheight
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 isfalse
, which increases the editing speed.fullscreen?: boolean=false
: Show markdown preview.overflow?: boolean=true
: Disablefullscreen
setting body stylespreview?: 'live' | 'edit' | 'preview'
: Default valuelive
, Show markdown preview.maxHeight?: number=1200
: Maximum drag height. ThevisibleDragbar=true
value is valid.minHeight?: number=100
: Minimum drag height. ThevisibleDragbar=true
value is valid.tabSize?: number=2
: The number of characters to insert when pressing tab key. Default2
spaces.defaultTabEnable?: boolean=false
: Iffalse
, thetab
key inserts a tab character into the textarea. Iftrue
, thetab
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.
- Install dependencies
$ npm install # Installation dependencies
$ npm run build # Compile all package
- 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`
- Launch documentation site
npm run start
- @uiw/react-textarea-code-editor: A simple code editor with syntax highlighting.
- @uiw/react-md-editor: A simple markdown editor with preview, implemented with React.js and TypeScript.
- @uiw/react-codemirror: CodeMirror component for React. @codemirror
- @uiw/react-monacoeditor: Monaco Editor component for React.
- @uiw/react-markdown-editor: A markdown editor with preview, implemented with React.js and TypeScript.
- @uiw/react-markdown-preview: React component preview markdown text in web browser.
As always, thanks to our amazing contributors!
Made with contributors.
Licensed under the MIT License.