This example previews Markdown syntax with Plate decorations. It keeps the Markdown characters in text nodes and applies CSS classes to matching ranges.
The demo tokenizes each text node with Prism's Markdown grammar and returns decoration ranges for token types such as title, bold, italic, blockquote, list, horizontal rule, and code.
'use client';
import { TextApi } from 'platejs';
import { definePlugin, EditorRoot, useCreateEditor } from 'platejs/react';
import Prism, { type TokenStream } from 'prismjs';
import * as React from 'react';
import { cn } from '@/lib/utils';
import { BasicNodesKit } from '@/components/editor/basic-nodes';
import { Editor, EditorContainer } from '@/components/editor/editor';
import { previewMdValue } from '@/registry/examples/values/preview-md-value';
import 'prismjs/components/prism-markdown.js';
The value is plain Plate content whose paragraph text includes Markdown characters.
/** @jsxRuntime classic */
/** @jsx jsx */
import { jsx } from '@platejs/test';
import type { Value } from 'platejs';
jsx;
export const previewMdValue: Value = (
<fragment>
<hheading level={2}>👀 Preview Markdown</hheading>
<hp>
Plate is flexible enough to add **decorations** that can format text based
on its content. For example, this editor has **Markdown** preview
decorations on it, to make it _dead_ simple to make an editor with
built-in `Markdown` previewing.
</hp>
| Surface | Owner | Notes |
|---|---|---|
PreviewMarkdownPlugin | Registry example | Tokenizes each text node and returns keyed ranges with token-specific className and data-preview-markdown attributes. |
BasicNodesKit | Registry kit | Supplies the editor's normal paragraph and heading plugins. |
prismjs | Dependency | Supplies the Markdown tokenizer used by the decoration function. |
Use this pattern when the editor should keep raw Markdown characters visible. Use Markdown when the editor should convert Markdown text into Plate nodes.
decorate.'use client';
import { TextApi } from 'platejs';
import { definePlugin, EditorRoot, useCreateEditor } from 'platejs/react';
import Prism, { type TokenStream } from 'prismjs';
import * as React from 'react';
import { cn } from '@/lib/utils';
import { BasicNodesKit } from '@/components/editor/basic-nodes';
import { Editor, EditorContainer } from '@/components/editor/editor';
import { previewMdValue } from '@/registry/examples/values/preview-md-value';
import 'prismjs/components/prism-markdown.js';
const PreviewMarkdownPlugin = definePlugin('previewMarkdown', {
decorate: {
read: ({ entry: [node, path] }) => {
if (!TextApi.isText(node)) return [];
const getLength = (token: TokenStream): number => {
if (typeof token === 'string') return token.length;
if (Array.isArray(token)) {
return token.reduce((length, child) => length + getLength(child), 0);
}
if (typeof token.content === 'string') return token.content.length;
return getLength(token.content);
};
const decorations = [];
const tokens = Prism.tokenize(node.text, Prism.languages.markdown);
let start = 0;
for (const [index, token] of tokens.entries()) {
const length = getLength(token);
const end = start + length;
if (typeof token !== 'string') {
decorations.push({
attributes: {
className: cn(
token.type === 'bold' && 'font-bold',
token.type === 'italic' && 'italic',
token.type === 'title' &&
'mx-0 mt-5 mb-2.5 inline-block font-bold text-[20px]',
token.type === 'list' && 'pl-2.5 text-[20px] leading-[10px]',
token.type === 'hr' &&
'block border-[#ddd] border-b-2 text-center',
token.type === 'blockquote' &&
'inline-block border-[#ddd] border-l-2 pl-2.5 text-[#aaa] italic',
token.type === 'code' && 'bg-[#eee] p-[3px] font-mono'
),
'data-preview-markdown': token.type,
},
key: `${path.join('.')}:${index}:${start}:${end}:${token.type}`,
range: {
anchor: { offset: start, path },
focus: { offset: end, path },
},
});
}
start = end;
}
return decorations;
},
},
});
export default function PreviewMdDemo() {
const editor = useCreateEditor(
{
plugins: [...BasicNodesKit, PreviewMarkdownPlugin],
initialValue: previewMdValue,
},
[]
);
return (
<EditorRoot editor={editor}>
<EditorContainer>
<Editor />
</EditorContainer>
</EditorRoot>
);
}/** @jsxRuntime classic */
/** @jsx jsx */
import { jsx } from '@platejs/test';
import type { Value } from 'platejs';
jsx;
export const previewMdValue: Value = (
<fragment>
<hheading level={2}>👀 Preview Markdown</hheading>
<hp>
Plate is flexible enough to add **decorations** that can format text based
on its content. For example, this editor has **Markdown** preview
decorations on it, to make it _dead_ simple to make an editor with
built-in `Markdown` previewing.
</hp>
<hp>- List item.</hp>
<hp>> Blockquote paragraph.</hp>
<hp>> > Nested blockquote.</hp>
<hp>> - Quoted list item.</hp>
<hp>---</hp>
<hp>## Try it out!</hp>
<hp>Try it out for yourself!</hp>
</fragment>
);