Start by adding the core Editor component to your project:
Next, create a basic editor in your main application file (e.g. src/App.tsx). This example sets up a simple editor within an EditorContainer.
import { Plate, usePlateEditor } from 'platejs/react';
import { Editor, EditorContainer } from '@/components/editor/editor';
export default function App() {
const editor = usePlateEditor();
return (
<Plate editor={editor}> {/* Provides editor context */}
<EditorContainer> {/* Styles the editor area */}
<Editor placeholder="Type your amazing content here..." />
</EditorContainer>
</Plate>
);
}import { Plate, usePlateEditor } from 'platejs/react';
import { Editor, EditorContainer } from '@/components/editor/editor';
export default function App() {
const editor = usePlateEditor();
return (
<Plate editor={editor}> {/* Provides editor context */}
<EditorContainer> {/* Styles the editor area */}
<Editor placeholder="Type your amazing content here..." />
</EditorContainer>
</Plate>
);
}usePlateEditor creates a memoized editor instance, ensuring stability across re-renders. For a non-memoized version, use createPlateEditor.
Enhance your editor with text formatting. Add the Basic Nodes Kit, FixedToolbar and MarkToolbarButton components:
The basic-nodes includes all the basic plugins (bold, italic, underline, headings, blockquotes, etc.) and their components that we'll use in the following steps.
Update your src/App.tsx to include these components and the basic mark plugins.
This example adds bold, italic, and underline functionality.
import * as React from 'react';
import type { Value } from 'platejs';
import {
BoldPlugin,
ItalicPlugin,
UnderlinePlugin,
} from '@platejs/basic-nodes/react';
import {
Plate,
usePlateEditor,
} from 'platejs/react';
import { Editor, EditorContainer } from '@/components/editor/editor';
import { FixedToolbar } from '@/components/editor/fixed-toolbar';
import { MarkToolbarButton } from '@/components/editor/mark-toolbar-button';
const initialValue: Value = [
{
type: 'paragraph',
children: [
{ text: 'Hello! Try out the ' },
{ text: 'bold', bold: true },
{ text: ', ' },
{ text: 'italic', italic: true },
{ text: ', and ' },
{ text: 'underline', underline: true },
{ text: ' formatting.' },
],
},
];
export default function App() {
const editor = usePlateEditor({
plugins: [BoldPlugin, ItalicPlugin, UnderlinePlugin], // Add the mark plugins
initialValue,
});
return (
<Plate editor={editor}>
<FixedToolbar className="justify-start rounded-t-lg">
<MarkToolbarButton nodeType="bold" tooltip="Bold (⌘+B)">B</MarkToolbarButton>
<MarkToolbarButton nodeType="italic" tooltip="Italic (⌘+I)">I</MarkToolbarButton>
<MarkToolbarButton nodeType="underline" tooltip="Underline (⌘+U)">U</MarkToolbarButton>
</FixedToolbar>
<EditorContainer>
<Editor placeholder="Type your amazing content here..." />
</EditorContainer>
</Plate>
);
}import * as React from 'react';
import type { Value } from 'platejs';
import {
BoldPlugin,
ItalicPlugin,
UnderlinePlugin,
} from '@platejs/basic-nodes/react';
import {
Plate,
usePlateEditor,
} from 'platejs/react';
import { Editor, EditorContainer } from '@/components/editor/editor';
import { FixedToolbar } from '@/components/editor/fixed-toolbar';
import { MarkToolbarButton } from '@/components/editor/mark-toolbar-button';
const
Introduce block-level elements like headings and blockquotes with custom components.
import * as React from 'react';
import type { Value } from 'platejs';
import {
BlockquotePlugin,
BoldPlugin,
HeadingPlugin,
ItalicPlugin,
UnderlinePlugin,
} from '@platejs/basic-nodes/react';
import {
Plate,
usePlateEditor,
} from 'platejs/react';
import { BlockquoteElement } from '@/components/editor/blockquote';
import { Editor, EditorContainer } from '@/components/editor/editor';
import { FixedToolbar } from '@/components/editor/fixed-toolbar';
import { HeadingElement, HeadingElement } from '@/components/editor/heading';
import { MarkToolbarButton } from '@/components/editor/mark-toolbar-button';
import { ToolbarButton } from '@/components/editor/toolbar'; // Generic toolbar button
const initialValue: Value = [
{
children: [{ text: 'Title' }],
type: 'heading', level: 3,
},
{
children: [
{
children: [{ text: 'This is a quote.' }],
type: 'paragraph',
},
],
type: 'blockquote',
},
{
children: [
{ text: 'With some ' },
{ bold: true, text: 'bold' },
{ text: ' text for emphasis!' },
],
type: 'paragraph',
},
];
export default function App() {
const editor = usePlateEditor({
plugins: [
BoldPlugin,
ItalicPlugin,
UnderlinePlugin,
HeadingPlugin.configure({ component: HeadingElement }),
HeadingPlugin.configure({ component: HeadingElement }),
HeadingPlugin.configure({ component: HeadingElement }),
BlockquotePlugin.configure({ component: BlockquoteElement }),
],
initialValue,
});
return (
<Plate editor={editor}>
<FixedToolbar className="flex justify-start gap-1 rounded-t-lg">
{/* Element Toolbar Buttons */}
<ToolbarButton onClick={() => editor.plugin(HeadingPlugin).update.toggle({ level: 1 })}>H1</ToolbarButton>
<ToolbarButton onClick={() => editor.plugin(HeadingPlugin).update.toggle({ level: 2 })}>H2</ToolbarButton>
<ToolbarButton onClick={() => editor.plugin(HeadingPlugin).update.toggle({ level: 3 })}>H3</ToolbarButton>
<ToolbarButton onClick={() => editor.plugin(BlockquotePlugin).update.toggle()}>Quote</ToolbarButton>
{/* Mark Toolbar Buttons */}
<MarkToolbarButton nodeType="bold" tooltip="Bold (⌘+B)">B</MarkToolbarButton>
<MarkToolbarButton nodeType="italic" tooltip="Italic (⌘+I)">I</MarkToolbarButton>
<MarkToolbarButton nodeType="underline" tooltip="Underline (⌘+U)">U</MarkToolbarButton>
</FixedToolbar>
<EditorContainer>
<Editor placeholder="Type your amazing content here..." />
</EditorContainer>
</Plate>
);
}import * as React from 'react';
import type { Value } from 'platejs';
import {
BlockquotePlugin,
BoldPlugin,
HeadingPlugin,
ItalicPlugin,
UnderlinePlugin,
} from '@platejs/basic-nodes/react';
import {
Plate,
usePlateEditor,
} from 'platejs/react';
import { BlockquoteElement } from '@/components/editor/blockquote';
import { Editor, EditorContainer } from '@/components/editor/editor';
import { FixedToolbar }
Notice how we use Plugin.configure({ component: Component }) to register components with their respective plugins. This is the recommended approach for associating React components with Plate plugins.
For a quicker start with common plugins and components pre-configured, use the editor-basic block:
This handles much of the boilerplate for you.
To make the editor content persistent, let's integrate localStorage to save and load the editor's value.
import * as React from 'react';
import type { Value } from 'platejs';
import {
BlockquotePlugin,
BoldPlugin,
HeadingPlugin,
ItalicPlugin,
UnderlinePlugin,
} from '@platejs/basic-nodes/react';
import {
Plate,
usePlateEditor,
} from 'platejs/react';
import { BlockquoteElement } from '@/components/editor/blockquote';
import { Editor, EditorContainer } from '@/components/editor/editor';
import { FixedToolbar } from '@/components/editor/fixed-toolbar';
import { HeadingElement, HeadingElement } from '@/components/editor/heading';
import { MarkToolbarButton } from '@/components/editor/mark-toolbar-button';
import { ToolbarButton } from '@/components/editor/toolbar';
const initialValue: Value = [
{
children: [{ text: 'Title' }],
type: 'heading', level: 3,
},
{
children: [
{
children: [{ text: 'This is a quote.' }],
type: 'paragraph',
},
],
type: 'blockquote',
},
{
children: [
{ text: 'With some ' },
{ bold: true, text: 'bold' },
{ text: ' text for emphasis!' },
],
type: 'paragraph',
},
];
export default function App() {
const editor = usePlateEditor({
plugins: [
BoldPlugin,
ItalicPlugin,
UnderlinePlugin,
HeadingPlugin.configure({ component: HeadingElement }),
HeadingPlugin.configure({ component: HeadingElement }),
HeadingPlugin.configure({ component: HeadingElement }),
BlockquotePlugin.configure({ component: BlockquoteElement }),
],
initialValue: () => {
const savedValue = localStorage.getItem('installation-react-demo');
return savedValue ? JSON.parse(savedValue) : initialValue;
},
});
return (
<Plate
editor={editor}
onValueChange={({ value }) => {
localStorage.setItem('installation-react-demo', JSON.stringify(value));
}}
>
<FixedToolbar className="flex justify-start gap-1 rounded-t-lg">
<ToolbarButton onClick={() => editor.plugin(HeadingPlugin).update.toggle({ level: 1 })}>H1</ToolbarButton>
<ToolbarButton onClick={() => editor.plugin(HeadingPlugin).update.toggle({ level: 2 })}>H2</ToolbarButton>
<ToolbarButton onClick={() => editor.plugin(HeadingPlugin).update.toggle({ level: 3 })}>H3</ToolbarButton>
<ToolbarButton onClick={() => editor.plugin(BlockquotePlugin).update.toggle()}>Quote</ToolbarButton>
<MarkToolbarButton nodeType="bold" tooltip="Bold (⌘+B)">B</MarkToolbarButton>
<MarkToolbarButton nodeType="italic" tooltip="Italic (⌘+I)">I</MarkToolbarButton>
<MarkToolbarButton nodeType="underline" tooltip="Underline (⌘+U)">U</MarkToolbarButton>
<div className="flex-1" />
<ToolbarButton
className="px-2"
onClick={() => {
editor.update((tx) => {
tx.value.replace({ children: initialValue });
});
}}
>
Reset
</ToolbarButton>
</FixedToolbar>
<EditorContainer>
<Editor placeholder="Type your amazing content here..." />
</EditorContainer>
</Plate>
);
}import * as React from 'react';
import type { Value } from 'platejs';
import {
BlockquotePlugin,
BoldPlugin,
HeadingPlugin,
ItalicPlugin,
UnderlinePlugin,
} from '@platejs/basic-nodes/react';
import {
Plate,
usePlateEditor,
} from 'platejs/react';
import { BlockquoteElement } from '@/components/editor/blockquote';
import { Editor, EditorContainer } from '@/components/editor/editor';
import { FixedToolbar } from '@/components/editor/fixed-toolbar'
Congratulations! You've built a foundational Plate editor in your project.
To further enhance your editor:
npx shadcn@latest add @plate/editor-basicnpx shadcn@latest add @plate/editor-ai