Plate
PlateEditorsTemplates
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 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.

src/App.tsx
import { EditorRoot, useCreateEditor } from 'platejs/react';
 
import { Editor, EditorContainer, EditorFrame } from '@/components/editor/editor';
 
export default function App() {
  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>
  );
}
src/App.tsx
import { EditorRoot, useCreateEditor } from 'platejs/react';
 
import { Editor, EditorContainer, EditorFrame } from '@/components/editor/editor';
 
export default function App() {
  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

useCreateEditor creates a memoized editor instance, ensuring stability across re-renders. For a non-memoized version, use createEditor.

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


































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



























































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/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 App() {
  const editor = useCreateEditor({
    plugins: [
      BoldPlugin,
      ItalicPlugin,
      UnderlinePlugin,
      HeadingPlugin.configure({ component: HeadingElement }),
      BlockquotePlugin.configure({ component: BlockquoteElement }),
    ],
    initialValue: () => {
      const savedValue = localStorage.getItem('installation-react-demo');
      return savedValue ? JSON.parse(savedValue) : initialValue;
    },
  });
 
  return (
    <EditorRoot
      editor={editor}
      onValueChange={({ value }) => {
        localStorage.setItem('installation-react-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>
  );
}
src/App.tsx
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












































































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
>
);
}
=
[
{
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 = 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>
);
}
{ 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 = 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>
);
}
{ 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 = useCreateEditor({
plugins: [
BoldPlugin,
ItalicPlugin,
UnderlinePlugin,
HeadingPlugin.configure({ component: HeadingElement }),
BlockquotePlugin.configure({ component: BlockquoteElement }),
],
initialValue: () => {
const savedValue = localStorage.getItem('installation-react-demo');
return savedValue ? JSON.parse(savedValue) : initialValue;
},
});
return (
<EditorRoot
editor={editor}
onValueChange={({ value }) => {
localStorage.setItem('installation-react-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>
);
}