Start by adding the core Editor component to your project:
Next, create a basic editor page. This example includes EditorFrame for panel
layout and EditorContainer for scrolling. Both are optional. A minimal,
textarea-like editor can render Editor directly inside EditorRoot. Use
variant="none" and your own padding and minimum height for that smaller surface.
See Editor layout for all three
compositions and where to set a fixed height.
'use client';
import { EditorRoot, useCreateEditor } from 'platejs/react';
import { Editor, EditorContainer, EditorFrame } from '@/components/editor/editor';
export default function MyEditorPage() {
const editor = useCreateEditor();
return (
<EditorRoot editor={editor}> {/* Provides editor context */}
<EditorFrame>
<EditorContainer> {/* Styles the editor area */}
<Editor placeholder="Type your amazing content here..." />
</EditorContainer>
</EditorFrame>
</EditorRoot>
);
}'use client';
import { EditorRoot, useCreateEditor } from 'platejs/react';
import { Editor, EditorContainer, EditorFrame } from '@/components/editor/editor';
export default function MyEditorPage() {
const editor = useCreateEditor();
return (
<EditorRoot editor={editor}> {/* Provides editor context */}
<EditorFrame>
<EditorContainer> {/* Styles the editor area */}
<Editor placeholder="Type your amazing content here..." />
</EditorContainer>
</EditorFrame
useCreateEditor creates a memoized editor instance, ensuring stability across re-renders. For a non-memoized version, use createEditor.
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 editor page to include these components and the basic mark plugins. This example adds bold, italic, and underline functionality.
'use client';
import * as React from 'react';
import type { Value } from 'platejs';
import {
BoldPlugin,
ItalicPlugin,
UnderlinePlugin,
} from 'platejs/react';
import {
EditorRoot,
useCreateEditor,
} from 'platejs/react';
import { Editor, EditorContainer, EditorFrame } 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 MyEditorPage() {
const editor = useCreateEditor({
plugins: [BoldPlugin, ItalicPlugin, UnderlinePlugin], // Add the mark plugins
initialValue,
});
return (
<EditorRoot editor={editor}>
<EditorFrame>
<FixedToolbar className="justify-start rounded-t-lg">
<MarkToolbarButton plugin={BoldPlugin} tooltip="Bold (⌘+B)">B</MarkToolbarButton>
<MarkToolbarButton plugin={ItalicPlugin} tooltip="Italic (⌘+I)">I</MarkToolbarButton>
<MarkToolbarButton plugin={UnderlinePlugin} tooltip="Underline (⌘+U)">U</MarkToolbarButton>
</FixedToolbar>
<EditorContainer>
<Editor placeholder="Type your amazing content here..." />
</EditorContainer>
</EditorFrame>
</EditorRoot>
);
}'use client';
import * as React from 'react';
import type { Value } from 'platejs';
import {
BoldPlugin,
ItalicPlugin,
UnderlinePlugin,
} from 'platejs/react';
import {
EditorRoot,
useCreateEditor,
} from 'platejs/react';
import { Editor, EditorContainer, EditorFrame } from '@/components/editor/editor';
import { FixedToolbar } from '@/components/editor/fixed-toolbar';
import { MarkToolbarButton } from '@/components/editor/mark-toolbar-button';
Introduce block-level elements like headings and blockquotes with custom components.
'use client';
import * as React from 'react';
import type { Value } from 'platejs';
import {
BlockquotePlugin,
BoldPlugin,
HeadingPlugin,
ItalicPlugin,
UnderlinePlugin,
} from 'platejs/react';
import {
EditorRoot,
useCreateEditor,
} from 'platejs/react';
import { BlockquoteElement } from '@/components/editor/blockquote';
import { Editor, EditorContainer, EditorFrame } from '@/components/editor/editor';
import { FixedToolbar } from '@/components/editor/fixed-toolbar';
import { 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 MyEditorPage() {
const editor = useCreateEditor({
plugins: [
BoldPlugin,
ItalicPlugin,
UnderlinePlugin,
HeadingPlugin.configure({ component: HeadingElement }),
BlockquotePlugin.configure({ component: BlockquoteElement }),
],
initialValue,
});
return (
<EditorRoot editor={editor}>
<EditorFrame>
<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 plugin={BoldPlugin} tooltip="Bold (⌘+B)">B</MarkToolbarButton>
<MarkToolbarButton plugin={ItalicPlugin} tooltip="Italic (⌘+I)">I</MarkToolbarButton>
<MarkToolbarButton plugin={UnderlinePlugin} tooltip="Underline (⌘+U)">U</MarkToolbarButton>
</FixedToolbar>
<EditorContainer>
<Editor placeholder="Type your amazing content here..." />
</EditorContainer>
</EditorFrame>
</EditorRoot>
);
}'use client';
import * as React from 'react';
import type { Value } from 'platejs';
import {
BlockquotePlugin,
BoldPlugin,
HeadingPlugin,
ItalicPlugin,
UnderlinePlugin,
} from 'platejs/react';
import {
EditorRoot,
useCreateEditor,
} from 'platejs/react';
import { BlockquoteElement } from '@/components/editor/blockquote';
import { Editor, EditorContainer, EditorFrame } from '@/components/editor/editor';
import { FixedToolbar } from
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 client-side.
'use client';
import * as React from 'react';
import type { Value } from 'platejs';
import {
BlockquotePlugin,
BoldPlugin,
HeadingPlugin,
ItalicPlugin,
UnderlinePlugin,
} from 'platejs/react';
import {
EditorRoot,
useCreateEditor,
} from 'platejs/react';
import { BlockquoteElement } from '@/components/editor/blockquote';
import { Editor, EditorContainer, EditorFrame } from '@/components/editor/editor';
import { FixedToolbar } from '@/components/editor/fixed-toolbar';
import { 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 MyEditorPage() {
const editor = useCreateEditor({
plugins: [
BoldPlugin,
ItalicPlugin,
UnderlinePlugin,
HeadingPlugin.configure({ component: HeadingElement }),
BlockquotePlugin.configure({ component: BlockquoteElement }),
],
initialValue: () => {
const savedValue = localStorage.getItem('installation-next-demo');
return savedValue ? JSON.parse(savedValue) : initialValue;
},
});
return (
<EditorRoot
editor={editor}
onValueChange={({ value }) => {
localStorage.setItem('installation-next-demo', JSON.stringify(value));
}}
>
<EditorFrame>
<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 plugin={BoldPlugin} tooltip="Bold (⌘+B)">B</MarkToolbarButton>
<MarkToolbarButton plugin={ItalicPlugin} tooltip="Italic (⌘+I)">I</MarkToolbarButton>
<MarkToolbarButton plugin={UnderlinePlugin} 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>
</EditorFrame>
</EditorRoot>
);
}'use client';
import * as React from 'react';
import type { Value } from 'platejs';
import {
BlockquotePlugin,
BoldPlugin,
HeadingPlugin,
ItalicPlugin,
UnderlinePlugin,
} from 'platejs/react';
import {
EditorRoot,
useCreateEditor,
} from 'platejs/react';
import { BlockquoteElement } from '@/components/editor/blockquote';
import { Editor, EditorContainer, EditorFrame } from '@/components/editor/editor';
import { FixedToolbar } from
Congratulations! You've built a foundational Plate editor in Next.js.
To further enhance your editor:
npx shadcn@latest add @plate/editor-basicnpx shadcn@latest add @plate/editor-ai