From zbeyens. The source code is available on GitHub.

Plate
PlatePliteEditorsTemplates
GitHub16kGitHub
DiscordDiscord
  • Introduction
  • Installation
    • Plate UI
      • Next.js
      • React
    • Manual
    • RSC
    • Node.js
    • Local Docs
    • MCP
  • Releases

React

PreviousNext

Install and configure Plate UI for React

Prerequisites

Before you begin, ensure you have installed and configured shadcn/ui (adapted for your framework, e.g., Vite) and Plate UI.

This guide walks you through incrementally building a Plate editor in your project.

Create Your First Editor

Next.jsManual

On This Page

Create Your First EditorAdding Basic MarksAdding Basic ElementsHandling Editor ValueNext Steps
Build your editor
Production-ready AI template and reusable components.
Get all-access

Start by adding the core Editor component to your project:

pnpm dlx shadcn@latest add @plate/editor
pnpm dlx shadcn@latest add @plate/editor

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.

src/App.tsx
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>
  );
}
src/App.tsx
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.

Loading…

Adding Basic Marks

Enhance your editor with text formatting. Add the Basic Nodes Kit, FixedToolbar and MarkToolbarButton components:

pnpm dlx shadcn@latest add @plate/basic-nodes @plate/fixed-toolbar @plate/mark-toolbar-button
pnpm dlx shadcn@latest add @plate/basic-nodes @plate/fixed-toolbar @plate/mark-toolbar-button

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.

src/App.tsx
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>
  );
}
src/App.tsx
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
































Loading…

Adding Basic Elements

Introduce block-level elements like headings and blockquotes with custom components.

src/App.tsx
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>
  );
}
src/App.tsx
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 } 




























































Loading…
Component Registration

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:

pnpm dlx shadcn@latest add @plate/editor-basic
pnpm dlx shadcn@latest add @plate/editor-basic

This handles much of the boilerplate for you.

Handling Editor Value

To make the editor content persistent, let's integrate localStorage to save and load the editor's value.

src/App.tsx
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>
  );
}
src/App.tsx
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'













































































Loading…

Next Steps

Congratulations! You've built a foundational Plate editor in your project.

To further enhance your editor:

  • Explore Components: Discover Toolbars, Menus, Node components, and more.
  • Add Plugins: Integrate features like Tables, Mentions, AI, or Markdown.
  • Use Editor Blocks: Quickly set up pre-configured editors:
    • Basic editor: npx shadcn@latest add @plate/editor-basic
    • AI-powered editor: npx shadcn@latest add @plate/editor-ai
  • Learn More:
    • Editor Configuration
    • Plugin Configuration
    • Plugin Components
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>
);
}
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 { 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>
);
}