ra-markdown: Markdown Utilities For React-Admin
For applications that need to display and edit Markdown content, this package offers two components:
<MarkdownField>: Display formatted markdown, using the Material UI theme<MarkdownInput>: Edit a markdown field, with a WYSIWYG editor and a markdown source editor with live preview
The editor is based on Milkdown, and its colors and fonts follow the Material UI theme, including the dark mode.
Test it live on the Enterprise Edition Storybook, and in the e-commerce demo.
Installation
npm install --save @react-admin/ra-markdown
# or
yarn add @react-admin/ra-markdown
Tip: ra-markdown is part of the React-Admin Enterprise Edition, and hosted in a private npm registry. You need to subscribe to one of the Enterprise Edition plans to access this package.
<MarkdownField>
A Field component for Markdown content. To be used e.g. in Show views.

Usage
import { Show, SimpleShowLayout, TextField } from 'react-admin';
import { MarkdownField } from '@react-admin/ra-markdown';
const PostShow = () => (
<Show>
<SimpleShowLayout>
<TextField source="title" />
<MarkdownField source="description" />
</SimpleShowLayout>
</Show>
);
import { Show, SimpleShowLayout, TextField } from "react-admin";
import { MarkdownField } from "@react-admin/ra-markdown";
const PostShow = () => (
<Show>
<SimpleShowLayout>
<TextField source="title" />
<MarkdownField source="description" />
</SimpleShowLayout>
</Show>
);
The content is rendered with the same styles as in <MarkdownInput>, so that it looks the same in read and edit modes.
Tip: If you want to display raw (unformatted) markdown, use <TextField component="pre"> instead:
<TextField source="description" component="pre" />
<TextField source="description" component="pre" />;
Props
| Prop | Required | Type | Default | Description |
|---|---|---|---|---|
source |
Required | string |
The field name in the record. Supports paths, e.g. description.body |
|
label |
Optional | string |
The label. If not set, the label is inferred from the source |
|
className |
Optional | string |
The CSS class of the root element | |
configureMilkdown |
Optional | (crepe: Crepe) => void |
Called before the editor is created. See <MarkdownInput configureMilkdown> |
|
onReady |
Optional | (crepe: Crepe) => void |
Called once the editor is created | |
sx |
Optional | SxProps |
Custom styles |
<MarkdownField> also accepts the common field props.
<MarkdownInput>
An Input component for Markdown content. To be used in Edit and Create views.
It offers two edition modes, which users switch with the tabs at the bottom of the input:
- WYSIWYG: a rich text editor, with a toolbar on top. Users can also format the selected text with a floating toolbar, and insert blocks with the
/command. - Markdown: the markdown source, with syntax highlighting, and a live preview. The preview is also editable.
Usage
import { Edit, SimpleForm, TextInput } from 'react-admin';
import { MarkdownInput } from '@react-admin/ra-markdown';
const PostEdit = () => (
<Edit>
<SimpleForm>
<TextInput source="title" />
<MarkdownInput source="description" />
</SimpleForm>
</Edit>
);
import { Edit, SimpleForm, TextInput } from "react-admin";
import { MarkdownInput } from "@react-admin/ra-markdown";
const PostEdit = () => (
<Edit>
<SimpleForm>
<TextInput source="title" />
<MarkdownInput source="description" />
</SimpleForm>
</Edit>
);
The editor props can be grouped in an object, e.g. to share the same configuration between several inputs:
import { Edit, SimpleForm, TextInput } from 'react-admin';
import { MarkdownInput, MarkdownInputProps } from '@react-admin/ra-markdown';
const options: Partial<MarkdownInputProps> = {
previewStyle: 'tab',
height: '300px',
initialEditType: 'markdown',
useCommandShortcut: false,
};
const PostEdit = () => (
<Edit>
<SimpleForm>
<TextInput source="title" />
<MarkdownInput source="description" {...options} />
</SimpleForm>
</Edit>
);
import { Edit, SimpleForm, TextInput } from "react-admin";
import { MarkdownInput } from "@react-admin/ra-markdown";
const options = {
previewStyle: "tab",
height: "300px",
initialEditType: "markdown",
useCommandShortcut: false,
};
const PostEdit = () => (
<Edit>
<SimpleForm>
<TextInput source="title" />
<MarkdownInput source="description" {...options} />
</SimpleForm>
</Edit>
);
Props
| Prop | Required | Type | Default | Description |
|---|---|---|---|---|
source |
Required | string |
The field name in the record | |
configureMilkdown |
Optional | (crepe: Crepe) => void |
Called before the editor is created, e.g. to register plugins | |
onReady |
Optional | (crepe: Crepe) => void |
Called once the editor is created | |
fullWidth |
Optional | boolean |
true |
If true, the input expands to fill the form width |
height |
Optional | string | number |
512px |
The editor height |
helperText |
Optional | string |
The helper text to display under the input | |
initialEditType |
Optional | 'markdown' | 'wysiwyg' |
wysiwyg |
The edition mode displayed first |
label |
Optional | string | false |
The label. If not set, the label is inferred from the source |
|
previewStyle |
Optional | 'vertical' | 'tab' |
vertical |
How the markdown mode displays the source and the preview |
ref |
Optional | Ref<MarkdownEditorRef> |
Gives access to the editor instance | |
toolbarItems |
Optional | array |
The buttons of the toolbar | |
useCommandShortcut |
Optional | boolean |
true |
Whether the formatting keyboard shortcuts are enabled |
<MarkdownInput> also accepts the common input props.
configureMilkdown
The WYSIWYG editor is based on Milkdown. Its plugins must be registered before the editor is created, so the ref gives access to the editor too late. Use the configureMilkdown prop instead: it receives the Crepe instance before its creation, and lets you register plugins with crepe.editor.use(), or change the editor context with crepe.editor.config().
For instance, to add a remark plugin, and a ProseMirror plugin highlighting some text with decorations:
import { MarkdownInput } from '@react-admin/ra-markdown';
import { Plugin } from '@milkdown/kit/prose/state';
import { Decoration, DecorationSet } from '@milkdown/kit/prose/view';
import { $prose, $remark } from '@milkdown/kit/utils';
import remarkDirective from 'remark-directive';
const directive = $remark('remarkDirective', () => remarkDirective);
const highlight = $prose(
() =>
new Plugin({
props: {
decorations: state =>
DecorationSet.create(state.doc, [
Decoration.inline(1, 6, { class: 'highlight' }),
]),
},
})
);
const configureMilkdown = crepe => {
crepe.editor.use(directive).use(highlight);
};
const PostEdit = () => (
<Edit>
<SimpleForm>
<MarkdownInput
source="description"
configureMilkdown={configureMilkdown}
/>
</SimpleForm>
</Edit>
);
<MarkdownField> accepts the same prop.
Tip: configureMilkdown is only read when the editor is created: changing it afterwards has no effect. It only applies to the WYSIWYG editor, not to the source of the markdown mode.
fullWidth
The input fills the form width by default. Set the fullWidth prop to false to disable it:
<MarkdownInput source="description" fullWidth={false} />
height
The editor is 512px high by default. You can change it with the height prop, using any CSS unit, or a number of pixels:
<MarkdownInput source="description" height="300px" />
When the content is longer, the editor scrolls.
helperText
If you need to display a text below the editor (usually to explain the expected data to the user), use the helperText prop.
<MarkdownInput
source="description"
helperText="Enter a description of the post"
/>
initialEditType
The edition mode displayed first: wysiwyg (the default) or markdown. Users can switch modes with the tabs at the bottom of the input.
<MarkdownInput source="description" initialEditType="markdown" />
label
You can customize the label by setting the label prop. It is inferred from the source prop by default. Set it to false to hide the label.
<MarkdownInput source="description" label="Explanation" />
onReady
Called once the WYSIWYG editor is created, with the Crepe instance. It replaces the onLoad option of the Toast UI editor used by ra-markdown v5.
<MarkdownInput
source="description"
onReady={crepe => console.log(crepe.getMarkdown())}
/>
<MarkdownField> accepts the same prop.
Tip: To register Milkdown plugins, use configureMilkdown instead: onReady is called too late for that.
previewStyle
How the markdown mode displays the source and its preview. It has no effect in the WYSIWYG mode.
vertical(default): the source and the preview are displayed side by side.tab: the source and the preview are displayed in two tabs, Write and Preview, at the top of the input.
<MarkdownInput
source="description"
initialEditType="markdown"
previewStyle="tab"
/>
ref
If you need to interact with the editor, you can access it with a ref. Its getInstance() method returns the Crepe editor instance, or undefined until the editor is created.
The following example calls the getMarkdown method of the editor to display the markdown content.
import { useRef, useState } from 'react';
import { Button, Edit, SimpleForm } from 'react-admin';
import { Alert } from '@mui/material';
import { MarkdownEditorRef, MarkdownInput } from '@react-admin/ra-markdown';
export const PostEdit = () => {
const editorRef = useRef<MarkdownEditorRef>(null);
const [markdown, setMarkdown] = useState<string>();
return (
<Edit>
<SimpleForm>
<MarkdownInput source="body" ref={editorRef} />
<Button
label="Get markdown value"
onClick={() =>
setMarkdown(
editorRef.current?.getInstance()?.getMarkdown()
)
}
/>
{markdown && (
<Alert severity="success">Markdown: {markdown}</Alert>
)}
</SimpleForm>
</Edit>
);
};
import { useRef, useState } from "react";
import { Button, Edit, SimpleForm } from "react-admin";
import { Alert } from "@mui/material";
import { MarkdownInput } from "@react-admin/ra-markdown";
export const PostEdit = () => {
const editorRef = useRef(null);
const [markdown, setMarkdown] = useState();
return (
<Edit>
<SimpleForm>
<MarkdownInput source="body" ref={editorRef} />
<Button
label="Get markdown value"
onClick={() => setMarkdown(editorRef.current?.getInstance()?.getMarkdown())}
/>
{markdown && <Alert severity="success">Markdown: {markdown}</Alert>}
</SimpleForm>
</Edit>
);
};
Tip: To read or change the value of the input, prefer the form API (e.g. useFormContext().setValue()): the editor is updated when the form value changes.
source
Specifies the field of the record that the input should edit. It is required.
<Form record={{ id: 123, title: 'Hello, world!', body: '**Lorem Ipsum**' }}>
<MarkdownInput source="body" />
{/* default value is "**Lorem Ipsum**" */}
</Form>
toolbarItems
The WYSIWYG editor displays a toolbar with all the built-in buttons by default. Use the toolbarItems prop to choose its buttons, and to add your own. It accepts an array of button groups, separated by a divider in the toolbar.
The built-in buttons are referenced by name: heading, bold, italic, strike, code, ul, ol, task, link, image, table, codeblock, quote and hr.
A custom button is an object with:
name: a unique nameicon: the content of the button, as an SVG or HTML stringonClick: the function called on click. It receives the Milkdown context, to run editor commandsactive(optional): a function receiving the Milkdown context, returningtrueto highlight the button
The following example shows a toolbar with a few built-in buttons, and a custom button that displays an alert when clicked.
import { Edit, SimpleForm } from 'react-admin';
import { MarkdownInput } from '@react-admin/ra-markdown';
const PostEdit = () => (
<Edit>
<SimpleForm>
<MarkdownInput
source="description"
toolbarItems={[
['heading', 'bold', 'italic', 'strike'],
[
{
name: 'custom',
icon: '<i>Custom Button</i>',
onClick: () => alert('Custom Button action'),
},
],
]}
/>
</SimpleForm>
</Edit>
);
import { Edit, SimpleForm } from "react-admin";
import { MarkdownInput } from "@react-admin/ra-markdown";
const PostEdit = () => (
<Edit>
<SimpleForm>
<MarkdownInput
source="description"
toolbarItems={[
["heading", "bold", "italic", "strike"],
[
{
name: "custom",
icon: "<i>Custom Button</i>",
onClick: () => alert("Custom Button action"),
},
],
]}
/>
</SimpleForm>
</Edit>
);
Tip: The toolbar is only displayed in the WYSIWYG editor (and in the preview of the markdown mode), not in the markdown source.
Tip: toolbarItems is only read when the editor is created: changing it afterwards has no effect.
useCommandShortcut
The editor supports keyboard shortcuts to format the content (Mod is Cmd on macOS, and Ctrl elsewhere):
| Shortcut | Command |
|---|---|
Mod-b |
Bold |
Mod-i |
Italic |
Mod-e |
Inline code |
Mod-Alt-x |
Strikethrough |
Mod-Shift-b |
Blockquote |
Mod-Alt-c |
Code block |
Mod-Alt-0 |
Paragraph |
Mod-Alt-1 to Mod-Alt-6 |
Heading 1 to 6 |
Mod-Alt-7 |
Ordered list |
Mod-Alt-8 |
Bullet list |
Set useCommandShortcut to false to disable them. The other keys (e.g. Enter, Tab or undo / redo) still work.
<MarkdownInput source="description" useCommandShortcut={false} />
Tip: useCommandShortcut is only read when the editor is created: changing it afterwards has no effect.
Styling
<MarkdownInput> renders the following CSS classes, that you can target to customize its look, e.g. with the sx prop of a parent component:
| Class | Element |
|---|---|
RaMarkdownInput-editor |
The editor container, including the mode tabs |
RaMarkdownInput-modeSwitch |
The bar of mode tabs, at the bottom of the editor |
RaMarkdownInput-modeTab-markdown |
The Markdown mode tab |
RaMarkdownInput-modeTab-wysiwyg |
The WYSIWYG mode tab |
milkdown |
The WYSIWYG editor (use .milkdown .ProseMirror for its content) |
The active mode tab has the aria-pressed="true" attribute.
<SimpleForm
sx={{
'& .RaMarkdownInput-editor': { borderRadius: 0 },
'& .RaMarkdownInput-modeSwitch': { display: 'none' },
}}
>
<MarkdownInput source="description" />
</SimpleForm>
CHANGELOG
v6.0.1
2026-10-26
- Add the
configureMilkdownprop to<MarkdownInput>,<MarkdownField>and<MarkdownEditor>, to register Milkdown plugins before the editor creation - Add the
onReadyprop to<MarkdownInput>,<MarkdownField>and<MarkdownEditor>, called once the editor is created (replaces the Toast UIonLoadoption) - Fix
<MarkdownInput readOnly>and<MarkdownInput disabled>remaining editable
v6.0.0
2026-10-26
Breaking Changes
-
<MarkdownInput>and<MarkdownField>no longer accept the Toast UI editor and viewer options. Onlyheight,initialEditType,previewStyle,toolbarItemsanduseCommandShortcutare still supported. The other options (e.g.hideModeSwitch,placeholder,plugins,language,customHTMLRenderer,linkAttributes) and the event handlers (e.g.onLoad,onKeydown) are removed. -
The custom buttons of
toolbarItemsuse a new format: the button content is aniconstring instead of anelHTML element, and the click handler is passed asonClick. Thetooltipoption is removed.
-function createLastButton() {
- const button = document.createElement('button');
- button.innerHTML = `<i>Custom Button</i>`;
- button.addEventListener('click', () => {
- alert('Custom Button action');
- });
- return button;
-}
-
<MarkdownInput
source="description"
toolbarItems={[
['heading', 'bold', 'italic', 'strike'],
[
{
- el: createLastButton(),
- tooltip: 'Custom Command',
name: 'custom',
+ icon: '<i>Custom Button</i>',
+ onClick: () => alert('Custom Button action'),
},
],
]}
/>
-
The
indent,outdentandscrollSyncbuilt-in toolbar buttons are removed. -
The
refof<MarkdownInput>is now typedMarkdownEditorRef, and itsgetInstance()method returns a Crepe editor (orundefineduntil the editor is created) instead of a Toast UI editor.getMarkdown()still works, but the other Toast UI methods (e.g.setMarkdown(),insertText(),changeMode()) don't exist. To change the value, use the form API instead.
-import { Editor, MarkdownInput } from '@react-admin/ra-markdown';
+import { MarkdownEditorRef, MarkdownInput } from '@react-admin/ra-markdown';
+import { useFormContext } from 'react-hook-form';
-const editorRef = useRef<Editor>(null);
+const editorRef = useRef<MarkdownEditorRef>(null);
+const { setValue } = useFormContext();
-const markdown = editorRef.current.getInstance().getMarkdown();
+const markdown = editorRef.current?.getInstance()?.getMarkdown();
-editorRef.current.getInstance().setMarkdown('Hello world');
+setValue('body', 'Hello world');
- The
toastui-editor-*CSS classes no longer exist, and theRaMarkdownInputtheme key is no longer supported. If you customized the editor styles, target the newRaMarkdownInput-*classes (see the Styling section) or the Milkdown classes (e.g..milkdown .ProseMirror) instead, with thesxprop of<MarkdownField>or of a parent of<MarkdownInput>, or theRaMarkdownFieldandRaMarkdownEditortheme keys.
v5.2.0
2026-09-14
- Add support for Material UI v9
v5.1.2
2025-10-28
- Disable
toast-uitelemetry for<MarkdownField>and<MarkdownInput>
v5.1.1
2025-06-24
- Fix
<MarkdownField>not updating when its value changes
v5.1.0
2025-06-16
- Make components compatible with Material UI v7
v5.0.2
2025-03-21
- Fix
<MarkdownInput helperText>prop type
v5.0.1
2025-03-11
- Fix
<MarkdownInput>does not show its error state like other inputs
v5.0.0
2024-07-25
- Upgrade to react-admin v5
- [TypeScript]: Enable strictNullChecks
v4.2.0
2023-05-24
- Upgraded to react-admin
4.10.6
v4.1.5
2023-03-02
- (feat) Add dark mode support to
MarkdownInput - (fix) Fix
MarkdownInputshould supportlabel={false} - (doc) Fix
MarkdownInputcustom Editor options example
v4.1.4
2023-01-19
- (fix) Fix usage with React 18
v4.1.3
2023-01-16
- (fix) Fix
MarkdownInputdoes not apply value when it has changed from outside the input
v4.1.2
2022-11-15
- (fix) Fix emotion warning about potentially unsafe CSS pseudo class ":first-child" when doing SSR
v4.1.1
2022-10-18
- (fix) Fix compatibility with Next.JS (toastui's css is now embedded in ra-markdown rather than requiring a global css import)
v4.1.0
2022-10-17
- (fix) Bump to react-admin v4.4.3 and fix
MarkdownInput'sdefaultValuefollowing https://github.com/marmelab/react-admin/pull/8262- NOTICE - This version requires at least react-admin v4.4.3
v4.0.1
2022-06-08
- (fix) Update peer dependencies ranges (support React 18)
v4.0.0
2022-06-07
- Upgrade to react-admin v4
- Upgraded toast-ui/react-editor to v3
Breaking Changes
toast-ui/react-editor is now using ProseMirror. If you customized the editor or the viewer with plugins, you should follow their upgrade guide.
It also means we had to drop the default support for syntax highlighting. Indeed, the new toast-ui/react-editor requires to add a plugin for that (code-syntax-highlight) and to specify the languages to support.
import { Edit, EditProps, SimpleForm, TextInput } from 'react-admin';
import { MarkdownInput } from '@react-admin/ra-markdown';
+import codeSyntaxHighlight from '@toast-ui/editor-plugin-code-syntax-highlight';
+import 'prismjs/themes/prism.css';
+import '@toast-ui/editor-plugin-code-syntax-highlight/dist/toastui-editor-plugin-code-syntax-highlight.css';
+import Prism from 'prismjs';
+import 'prismjs/components/prism-jsx.js';
+ const options = {
+ plugins: [codeSyntaxHighlight]
+ };
const PostEdit = (props: EditProps) => (
<Edit {...props}>
<SimpleForm>
<TextInput source="title" />
<MarkdownInput
source="description"
+ options={options}
/>
</SimpleForm>
</Edit>
);
v1.3.2
2021-12-20
- (fix) MarkdownField can't process nested source
v1.3.1
2021-06-29
- (fix) Update peer dependencies ranges (support react 17)
v1.3.0
2021-06-15
- Add support for helperText and validation to
<MarkdownInput> - Export the props interfaces of both
<MarkdownInput>and<MarkdownField>
v1.2.0
2020-10-17
- Upgrade to react-admin
3.15 - (feat) Ensure
MarkdownFieldstyles are customizable through MUI theme by providing their key (RaMarkdownField).
v1.1.0
2020-10-05
- Upgrade to react-admin
3.9
v1.0.3
2020-09-30
- Update Readme
v1.0.2
2020-09-16
- (deps) Upgrade dependencies
v1.0.1
2020-08-31
- (fix) Fix
fullWidthsupport
v1.0.0
2020-07-31
- First release