Getting Started

A quick tutorial to get you up and running with Plate.

Create project

You can choose one of the following templates to get started:

OptionPlatePluginsAIBackend
Notion-like template
Plate playground template
Plate minimal template

For an existing React project, jump to the next step.

Add dependencies

First, install the core dependencies:

npm install @udecode/plate-common slate slate-dom slate-react slate-history

For the examples in this guide, we'll also use these plugins:

npm install @udecode/plate-basic-marks @udecode/plate-heading @udecode/plate-block-quote @udecode/cn
  • @udecode/plate-basic-marks provides bold, italic, underline, and code formatting.
  • @udecode/plate-heading adds h1-h6 support.
  • @udecode/plate-block-quote adds blockquote support.
  • @udecode/cn helps with component styling (optional).

Basic Editor

Let's start with a minimal editor setup.

import {
  usePlateEditor,
  Plate,
  PlateContent,
} from '@udecode/plate-common/react';
 
export default function BasicEditor() {
  const editor = usePlateEditor();
 
  return (
    <Plate editor={editor}>
      <PlateContent placeholder="Type..." />
    </Plate>
  );
}

Plate manages the editor state and PlateContent renders the editor content.

Loading...
components/basic-editor-default-demo.tsx
'use client';

import React from 'react';

import {
  Plate,
  PlateContent,
  usePlateEditor,
} from '@udecode/plate-common/react';

export default function BasicEditorDefaultDemo() {
  const editor = usePlateEditor();

  return (
    <Plate editor={editor}>
      <PlateContent placeholder="Type..." />
    </Plate>
  );
}

Styling

Let's give our editor some styles: Editor is a styled version of PlateContent.

Loading...
components/basic-editor-styling-demo.tsx
'use client';

import React from 'react';

import { Plate, usePlateEditor } from '@udecode/plate-common/react';

import { Editor, EditorContainer } from '@/components/plate-ui/editor';

export default function BasicEditorStylingDemo() {
  const editor = usePlateEditor();

  return (
    <Plate editor={editor}>
      <EditorContainer>
        <Editor placeholder="Type..." />
      </EditorContainer>
    </Plate>
  );
}

To keep things simple, we'll continue to use PlateContent in the following code snippets.

Initializing Editor's Value

Let's specify the initial content of the editor: a single paragraph.

// ...
 
const value = [
  {
    type: 'p',
    children: [
      {
        text: 'This is editable plain text with react and history plugins, just like a <textarea>!',
      },
    ],
  },
];
 
export default function BasicEditor() {
  const editor = usePlateEditor({
    value,
  });
 
  return (
    <Plate editor={editor}>
      <PlateContent />
    </Plate>
  );
}
Loading...
components/basic-editor-value-demo.tsx
'use client';

import React from 'react';

import { Plate, usePlateEditor } from '@udecode/plate-common/react';

import { Editor, EditorContainer } from '@/components/plate-ui/editor';

const value = [
  {
    children: [
      {
        text: 'This is editable plain text with react and history plugins, just like a <textarea>!',
      },
    ],
    type: 'p',
  },
];

export default function BasicEditorValueDemo() {
  const editor = usePlateEditor({ value });

  return (
    <Plate editor={editor}>
      <EditorContainer>
        <Editor />
      </EditorContainer>
    </Plate>
  );
}

Implementing Change Handler

At this stage, it's crucial to monitor editor modifications in order to store the values appropriately. The onChange prop will serve this purpose. You can also persist the editor's state by saving the value to local storage or a database and loading it back when needed.

// ...
 
export default function BasicEditor() {
  const localValue =
    typeof window !== 'undefined' && localStorage.getItem('editorContent');
 
  const editor = usePlateEditor({
    value: localValue ? JSON.parse(localValue) : value,
  });
 
  return (
    <Plate
      editor={editor}
      onChange={({ value }) => {
        // For performance, debounce your saving logic
        localStorage.setItem('editorContent', JSON.stringify(value));
      }}
    >
      <PlateContent />
    </Plate>
  );
}
Loading...
components/basic-editor-handler-demo.tsx
'use client';

import React, { useState } from 'react';

import type { Value } from '@udecode/plate-common';

import { Plate, usePlateEditor } from '@udecode/plate-common/react';

import {
  Accordion,
  AccordionContent,
  AccordionItem,
  AccordionTrigger,
} from '@/components/ui/accordion';
import { Editor, EditorContainer } from '@/components/plate-ui/editor';

const value = [
  {
    children: [
      {
        text: 'This is editable plain text with react and history plugins, just like a textarea!',
      },
    ],
    type: 'p',
  },
];

export default function BasicEditorHandlerDemo() {
  const [debugValue, setDebugValue] = useState<Value>(value);

  const localValue =
    typeof window !== 'undefined' && localStorage.getItem('editorContent');

  const editor = usePlateEditor({
    value: localValue ? JSON.parse(localValue) : value,
  });

  return (
    <Plate
      onChange={({ value }) => {
        localStorage.setItem('editorContent', JSON.stringify(value));
        setDebugValue(value);
      }}
      editor={editor}
    >
      <EditorContainer>
        <Editor />
      </EditorContainer>

      <Accordion type="single" collapsible>
        <AccordionItem value="manual-installation">
          <AccordionTrigger>Debug Value</AccordionTrigger>
          <AccordionContent>{JSON.stringify(debugValue)}</AccordionContent>
        </AccordionItem>
      </Accordion>
    </Plate>
  );
}

Plugins

Let's use a few basic plugins.

// ...
 
import {
  BoldPlugin,
  ItalicPlugin,
  UnderlinePlugin,
} from '@udecode/plate-basic-marks/react';
import { HeadingPlugin } from '@udecode/plate-heading/react';
import { BlockquotePlugin } from '@udecode/plate-block-quote/react';
 
const value = [
  // ...
];
 
export default function BasicEditor() {
  const editor = usePlateEditor({
    plugins: [
      HeadingPlugin,
      BlockquotePlugin,
      BoldPlugin,
      ItalicPlugin,
      UnderlinePlugin,
    ],
    value,
  });
 
  return (
    <Plate editor={editor}>
      <PlateContent />
    </Plate>
  );
}
Loading...
components/basic-plugins-default-demo.tsx
'use client';

import React, { useState } from 'react';

import type { Value } from '@udecode/plate-common';

import {
  BoldPlugin,
  CodePlugin,
  ItalicPlugin,
  UnderlinePlugin,
} from '@udecode/plate-basic-marks/react';
import { BlockquotePlugin } from '@udecode/plate-block-quote/react';
import { Plate, usePlateEditor } from '@udecode/plate-common/react';
import { HeadingPlugin } from '@udecode/plate-heading/react';

import {
  Accordion,
  AccordionContent,
  AccordionItem,
  AccordionTrigger,
} from '@/components/ui/accordion';
import { Editor, EditorContainer } from '@/components/plate-ui/editor';

import { basicEditorValue } from './basic-plugins-components-demo';

export default function BasicPluginsDefaultDemo() {
  const [debugValue, setDebugValue] = useState<Value>(basicEditorValue);
  const editor = usePlateEditor({
    plugins: [
      BlockquotePlugin,
      HeadingPlugin,
      BoldPlugin,
      ItalicPlugin,
      UnderlinePlugin,
      CodePlugin,
    ],
    value: basicEditorValue,
  });

  return (
    <Plate
      onChange={({ value }) => {
        setDebugValue(value);
        // save newValue...
      }}
      editor={editor}
    >
      <EditorContainer>
        <Editor />
      </EditorContainer>

      <Accordion type="single" collapsible>
        <AccordionItem value="manual-installation">
          <AccordionTrigger>Debug Value</AccordionTrigger>
          <AccordionContent>{JSON.stringify(debugValue)}</AccordionContent>
        </AccordionItem>
      </Accordion>
    </Plate>
  );
}

The plugins are functioning correctly. However, since we haven't specified any custom components for rendering, the editor is using the default (unstyled) components. Specifically, the default element component is a div, and the default leaf component is a span.

Components

To plug-in all the components in one place, use the override.components option in usePlateEditor. We'll use the withProps helper to pass additional props to the components with Tailwind CSS classes.

// ...
 
import { withProps } from '@udecode/cn';
import {
  Plate,
  PlateElement,
  PlateLeaf,
  usePlateEditor,
} from '@udecode/plate-common/react';
 
export default function BasicEditor() {
  const editor = usePlateEditor({
    plugins: [
      HeadingPlugin,
      BlockquotePlugin,
      BoldPlugin,
      ItalicPlugin,
      UnderlinePlugin,
    ],
    override: {
      components: {
        blockquote: withProps(PlateElement, {
          as: 'blockquote',
          className: 'mb-4 border-l-4 border-[#d0d7de] pl-4 text-[#636c76]',
        }),
        bold: withProps(PlateLeaf, { as: 'strong' }),
        h1: withProps(PlateElement, {
          as: 'h1',
          className:
            'mb-4 mt-6 text-3xl font-semibold tracking-tight lg:text-4xl',
        }),
        h2: withProps(PlateElement, {
          as: 'h2',
          className: 'mb-4 mt-6 text-2xl font-semibold tracking-tight',
        }),
        h3: withProps(PlateElement, {
          as: 'h3',
          className: 'mb-4 mt-6 text-xl font-semibold tracking-tight',
        }),
        italic: withProps(PlateLeaf, { as: 'em' }),
        p: withProps(PlateElement, {
          as: 'p',
          className: 'mb-4',
        }),
        underline: withProps(PlateLeaf, { as: 'u' }),
      },
    },
  });
 
  return (
    <Plate editor={editor}>
      <PlateContent />
    </Plate>
  );
}
Loading...
components/basic-plugins-components-demo.tsx
'use client';

import { withProps } from '@udecode/cn';
import {
  BoldPlugin,
  CodePlugin,
  ItalicPlugin,
  UnderlinePlugin,
} from '@udecode/plate-basic-marks/react';
import { BlockquotePlugin } from '@udecode/plate-block-quote/react';
import {
  Plate,
  PlateElement,
  PlateLeaf,
  usePlateEditor,
} from '@udecode/plate-common/react';
import { HeadingPlugin } from '@udecode/plate-heading/react';

import { Editor, EditorContainer } from '@/components/plate-ui/editor';

export default function BasicPluginsComponentsDemo() {
  const editor = usePlateEditor({
    override: {
      components: {
        blockquote: withProps(PlateElement, {
          as: 'blockquote',
          className: 'mb-4 border-l-4 border-[#d0d7de] pl-4 text-[#636c76]',
        }),
        bold: withProps(PlateLeaf, { as: 'strong' }),
        h1: withProps(PlateElement, {
          as: 'h1',
          className:
            'mb-4 mt-6 text-3xl font-semibold tracking-tight lg:text-4xl',
        }),
        h2: withProps(PlateElement, {
          as: 'h2',
          className: 'mb-4 mt-6 text-2xl font-semibold tracking-tight',
        }),
        h3: withProps(PlateElement, {
          as: 'h3',
          className: 'mb-4 mt-6 text-xl font-semibold tracking-tight',
        }),
        italic: withProps(PlateLeaf, { as: 'em' }),
        p: withProps(PlateElement, {
          as: 'p',
          className: 'mb-4',
        }),
        underline: withProps(PlateLeaf, { as: 'u' }),
      },
    },
    plugins: [
      BlockquotePlugin,
      HeadingPlugin,
      BoldPlugin,
      ItalicPlugin,
      UnderlinePlugin,
      CodePlugin,
    ],
    value: basicEditorValue,
  });

  return (
    <Plate editor={editor}>
      <EditorContainer>
        <Editor placeholder="Type..." autoFocus={false} spellCheck={false} />
      </EditorContainer>
    </Plate>
  );
}

export const basicEditorValue = [
  {
    id: '1',
    children: [
      {
        text: '🌳 Blocks',
      },
    ],
    type: 'h1',
  },
  {
    id: '2',
    children: [
      {
        text: 'Easily create headings of various levels, from H1 to H6, to structure your content and make it more organized.',
      },
    ],
    type: 'p',
  },
  {
    id: '3',
    children: [
      {
        text: 'Create blockquotes to emphasize important information or highlight quotes from external sources.',
      },
    ],
    type: 'blockquote',
  },
  {
    id: '1',
    children: [
      {
        text: '🌱 Marks',
      },
    ],
    type: 'h1',
  },
  {
    id: '2',
    children: [
      {
        text: 'Add style and emphasis to your text using the mark plugins, which offers a variety of formatting options.',
      },
    ],
    type: 'p',
  },
  {
    id: '3',
    children: [
      {
        text: 'Make text ',
      },
      {
        bold: true,
        text: 'bold',
      },
      {
        text: ', ',
      },
      {
        italic: true,
        text: 'italic',
      },
      {
        text: ', ',
      },
      {
        text: 'underlined',
        underline: true,
      },
      {
        text: ', or apply a ',
      },
      {
        bold: true,
        italic: true,
        text: 'combination',
        underline: true,
      },
      {
        text: ' of these styles for a visually striking effect.',
      },
    ],
    type: 'p',
  },
];

Initializing Editor's Value with HTML String

You can also specify the initial content of the editor using an HTML string and the corresponding plugins.

// ...
 
const htmlValue = '<p>This is <b>bold</b> and <i>italic</i> text!</p>';
 
export default function BasicEditor() {
  const editor = usePlateEditor({
    // ...
    value: htmlValue,
  });
 
  return (
    <Plate editor={editor}>
      <PlateContent />
    </Plate>
  );
}

That's it!

You can now play around with the Playground and start building your own editor.