This example previews Markdown syntax with Plite decorations. It does not deserialize Markdown into Plate nodes; it keeps the text as text and styles matching ranges with a custom leaf renderer.
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 type { DecoratedRange } from '@platejs/plite';
import { property, TextApi } from 'platejs';
import {
type PlateLeafProps,
definePlatePlugin,
Plate,
PlateLeaf,
usePlateEditor,
} 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
The value is plain Plate content whose paragraph text includes Markdown characters.
/** @jsxRuntime classic */
/** @jsx jsx */
import { jsx } from '@platejs/test-utils';
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 |
|---|---|---|
decoratePreview | Registry example | Reads text nodes, tokenizes node.text, and returns Plite ranges with token-type flags. |
PreviewLeaf | Registry example | Applies CSS classes when a decorated leaf has bold, italic, title, list, hr, blockquote, or code. |
preview-markdown plugin | Registry example |
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 type { DecoratedRange } from '@platejs/plite';
import { property, TextApi } from 'platejs';
import {
type PlateLeafProps,
definePlatePlugin,
Plate,
PlateLeaf,
usePlateEditor,
} 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 = definePlatePlugin('previewMarkdown', {
schema: {
mark: property.boolean({ default: false, omitDefault: true }),
},
decorate: ({ 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 ranges: Array<
DecoratedRange & {
blockquote?: boolean;
bold?: boolean;
code?: boolean;
hr?: boolean;
italic?: boolean;
list?: boolean;
previewMarkdown: boolean;
title?: boolean;
}
> = [];
const tokens = Prism.tokenize(node.text, Prism.languages.markdown);
let start = 0;
for (const token of tokens) {
const length = getLength(token);
const end = start + length;
if (typeof token !== 'string') {
ranges.push({
anchor: { offset: start, path },
blockquote: token.type === 'blockquote' || undefined,
bold: token.type === 'bold' || undefined,
code: token.type === 'code' || undefined,
focus: { offset: end, path },
hr: token.type === 'hr' || undefined,
italic: token.type === 'italic' || undefined,
list: token.type === 'list' || undefined,
previewMarkdown: true,
title: token.type === 'title' || undefined,
});
}
start = end;
}
return ranges;
},
});
function PreviewLeaf(props: PlateLeafProps<typeof PreviewMarkdownPlugin>) {
const { blockquote, bold, code, hr, italic, list, title } = props.leaf;
return (
<PlateLeaf
{...props}
className={cn(
bold && 'font-bold',
italic && 'italic',
title && 'mx-0 mt-5 mb-2.5 inline-block font-bold text-[20px]',
list && 'pl-2.5 text-[20px] leading-[10px]',
hr && 'block border-[#ddd] border-b-2 text-center',
blockquote &&
'inline-block border-[#ddd] border-l-2 pl-2.5 text-[#aaa] italic',
code && 'bg-[#eee] p-[3px] font-mono'
)}
/>
);
}
const PreviewMarkdownKit = PreviewMarkdownPlugin.configure({
component: PreviewLeaf,
});
export default function PreviewMdDemo() {
const editor = usePlateEditor(
{
plugins: [...BasicNodesKit, PreviewMarkdownKit],
initialValue: previewMdValue,
},
[]
);
return (
<Plate editor={editor}>
<EditorContainer>
<Editor />
</EditorContainer>
</Plate>
);
}/** @jsxRuntime classic */
/** @jsx jsx */
import { jsx } from '@platejs/test-utils';
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>
);Local defineBasePlugin(name, { decorate }) plugin. |
BasicNodesKit | Registry kit | Supplies the editor's normal paragraph and heading plugins. |
prismjs | Dependency | Supplies the Markdown tokenizer used by the decoration function. |