Skip to main content

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. onChange gives you back a Markdown string. Your application stores Markdown, not editor JSON.

Loading editor

The stylesheet is optional. Without it the editor is unstyled and fully functional. See Styling.

The editor needs a DOM, so it carries "use client". The renderer does not. See Server rendering.

Controlled and uncontrolled

Controlled, when the value lives in your 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 for the lifetime of the component. Switching between them logs an error in development.

In controlled mode the parent normally echoes back the value it has received. An echoed value is recognised and ignored, so it never resets your selection or your undo history.

A value that did not come from the editor is treated as a genuine external change, and replaces the content.

Debounce persistence outside the editor. onChange fires per edit, and that is the right granularity for the component.

Three modes

type MarkdownEditorMode = 'rich' | 'source' | 'preview'

rich is WYSIWYG editing. source is the Markdown text. preview is read-only rendered output.

All three project the same document, so switching modes never loses anything.

Controlled:

<MarkdownEditor mode={mode} onModeChange={setMode} value={value} onChange={setValue} />

Uncontrolled:

<MarkdownEditor defaultMode="source" value={value} onChange={setValue} />
Loading editor

Preview delegates to @react-markdown-kit/renderer. There is no second renderer inside the editor, so preview and your published page agree.

The preset

Pass the same preset the renderer uses, and the editor writes the dialect your application 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 resolve on every render, so changing the dialect at runtime works.

Document identity

onChange echoes are ordinary. Loading a different document is not.

<MarkdownEditor
documentKey={note.id}
value={note.markdown}
onChange={setMarkdown}
/>

Changing documentKey is a deliberate document replacement. The editor reloads the content and clears undo history, so the reader cannot undo their way into the previous note.

Leave documentKey alone and an edit is an edit, whatever the value does.

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 labels, so editor chrome can be localized independently of the document.

Loading editor

For a toolbar built entirely from your own components, see Headless editing.

Other props

PropDoes
readOnlyMakes the surface non-editable
placeholderShown while the document is empty
classNamesReplaces the rmk- class on a part of the chrome
componentsComponent overrides used by preview
labelsEditor chrome strings
onUploadImageApplication-owned image upload
onDiagnosticsCalled with parse diagnostics
aria-labelAccessible name for the editing surface

Editing is only safe to offer if saving does not damage the file. See Round-trip preservation.

Images need somewhere to live. See Images.