Editor basics
npm install @react-markdown-kit/editor'use client'
import { useState } from 'react'
import { MarkdownEditor } from '@react-markdown-kit/editor'
import '@react-markdown-kit/editor/styles.css'
export function Notes() {
const [value, setValue] = useState('# Notes\n\nStart writing.')
return <MarkdownEditor value={value} onChange={setValue} />
}value is a Markdown string and onChange gives you a Markdown string back, so what your app stores is plain Markdown (there's no editor JSON to keep around).
The stylesheet is optional. Without it the editor is unstyled but still works fine. See Styling.
The editor needs a DOM, so it carries "use client". The renderer doesn't. See Server rendering.
Controlled and uncontrolled#
Controlled, when the value lives in your own state:
<MarkdownEditor value={value} onChange={setValue} />Uncontrolled, when the editor owns the value and you only observe it:
<MarkdownEditor defaultValue="# Hello" onChange={handleChange} />With neither, the editor starts empty:
<MarkdownEditor />Pick one and stick with it for the lifetime of the component. Switching between them logs an error in development.
In controlled mode the parent usually passes back the same value it just got. The editor recognises that echo and ignores it, so it won't reset your selection or undo history.
A value that didn't come from the editor is treated as an external change and replaces the content.
If you need to debounce saving, do it outside the editor. onChange fires on every edit, which is the right granularity for the component itself.
Three modes#
type MarkdownEditorMode = 'rich' | 'source' | 'preview'rich is WYSIWYG editing. source is the Markdown text. preview is read-only rendered output.
All three are views of the same document, so switching modes doesn't lose anything.
Controlled:
<MarkdownEditor mode={mode} onModeChange={setMode} value={value} onChange={setValue} />Uncontrolled:
<MarkdownEditor defaultMode="source" value={value} onChange={setValue} />Preview uses @react-markdown-kit/renderer. There's no separate renderer inside the editor, so preview should match your published page.
The preset#
Pass the editor the same preset your renderer uses and it'll write the same Markdown dialect your app reads.
import { appMarkdown } from './markdown'
<MarkdownEditor preset={appMarkdown} value={value} onChange={setValue} />Without a preset the editor is CommonMark. With gfm() it edits tables, task lists, strikethrough, autolinks and footnotes. See Presets.
Extensions are resolved on every render, so you can change the dialect at runtime.
Document identity#
Echoing onChange back is the normal case. Loading a different document is a separate thing, and you tell the editor about it with documentKey.
<MarkdownEditor
documentKey={note.id}
value={note.markdown}
onChange={setMarkdown}
/>Changing documentKey tells the editor you've swapped documents. It reloads the content and clears undo history, so nobody can undo their way back into the previous note.
If documentKey stays the same, a new value is treated as an edit, whatever it looks like.
The toolbar#
The default toolbar covers marks, block types, lists, links, images, horizontal rules, undo, redo and the mode switch.
Remove it:
<MarkdownEditor toolbar={false} value={value} onChange={setValue} />Replace it with a render prop:
<MarkdownEditor
value={value}
onChange={setValue}
toolbar={(items, editor) => (
<div>
{items.map((item) => (
<button key={item.id} type="button" onClick={item.run} aria-pressed={item.active}>
{item.label}
</button>
))}
</div>
)}
/>Each item has id, label, icon, active, disabled, group and run(). Labels come from the labels prop, so you can localize the editor chrome separately from the document.
If you want a toolbar built entirely from your own components, see Headless editing.
Other props#
| Prop | Does |
|---|---|
readOnly | Makes the surface non-editable |
placeholder | Shown while the document is empty |
classNames | Replaces the rmk- class on a part of the chrome |
components | Component overrides used by preview |
labels | Editor chrome strings |
onUploadImage | Application-owned image upload |
onDiagnostics | Called with parse diagnostics |
aria-label | Accessible name for the editing surface |
Related#
Editing is only safe to offer if saving doesn't damage the file. See Round-trip preservation.
For uploads and where images get stored, see Images.
Read next#
The three modes and a round-trip check you can run on your own Markdown: Editor demo.
Last updated on