Getting Started
A quick tutorial to get you up and running with Plate.
Use our interactive builder to generate personalized installation steps.
Create project
You can choose one of the following templates to get started:
Option | NextJS | Tailwind | Plate | Plugins |
---|---|---|---|---|
Plate playground template | ✅ | ✅ | ✅ | ✅ |
Plate minimal template | ✅ | ✅ | ✅ | |
NextJS template | ✅ | ✅ |
For an existing project, jump to the next step.
Add dependencies
Install the core and the plugins you need. You need at least:
npm install @udecode/plate-common slate slate-react slate-history slate-hyperscript react react-dom
Alternatively you can install @udecode/plate
that contains all the packages excluding the ones with additional dependencies (e.g. @udecode/plate-dnd
).
npm install @udecode/plate slate slate-react slate-history slate-hyperscript react react-dom
Basic Editor
Let's start with a minimal editor setup.
import { Plate, PlateContent } from '@udecode/plate-common';
export default function BasicEditor() {
return (
<Plate>
<PlateContent placeholder="Type..." />
</Plate>
);
}
Plate
manages the editor state and PlateContent
renders the editor content.
Styling
Let's give our editor some styles: Editor is a styled version of PlateContent
.
import { Plate } from '@udecode/plate-common';
import { Editor } from '@/components/plate-ui/editor';
export default function BasicEditor() {
return (
<Plate>
<Editor placeholder="Type..." />
</Plate>
);
}
Note: Editor
is just an example of a styled editor using Tailwind. You can create your own styled version of PlateContent
.
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--card: 0 0% 100%;
--card-foreground: 240 10% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%;
--primary: 240 5.9% 10%;
--primary-foreground: 0 0% 98%;
--secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--destructive: 0 72.22% 50.59%;
--destructive-foreground: 0 0% 98%;
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--ring: 240 5% 64.9%;
--radius: 0.5rem;
}
.dark {
--background: 240 10% 3.9%;
--foreground: 0 0% 98%;
--card: 240 10% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 240 10% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 240 5.9% 10%;
--secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%;
--muted: 240 3.7% 15.9%;
--muted-foreground: 240 5% 64.9%;
--accent: 240 3.7% 15.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 85.7% 97.3%;
--border: 240 3.7% 25%;
--input: 240 3.7% 25%;
--ring: 240 3.7% 25%;
}
}
.slate-selection-container{
z-index: 9999 !important;
}
@layer base {
* {
@apply border-border;
}
/*html {*/
/* @apply scroll-smooth;*/
/*}*/
body {
@apply bg-background text-foreground;
font-feature-settings:
'rlig' 1,
'calt' 1;
/*font-synthesis-weight: none;*/
/*text-rendering: optimizeLegibility;*/
}
}
@layer utilities {
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.no-scrollbar {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
.step {
counter-increment: step;
}
.step:before {
@apply absolute inline-flex h-9 w-9 items-center justify-center rounded-full border-4 border-background bg-muted text-center -indent-px font-mono text-base font-medium;
@apply ml-[-50px] mt-[-4px];
content: counter(step);
}
.chunk-container {
@apply shadow-none;
}
.chunk-container::after {
content: '';
@apply absolute -inset-4 shadow-xl rounded-xl border;
}
}
@media (max-width: 640px) {
.container {
@apply px-4;
}
}
Initializing Editor's Value
Let's specify the initial content of the editor: a single paragraph.
// ...
const initialValue = [
{
type: 'p',
children: [
{
text: 'This is editable plain text with react and history plugins, just like a <textarea>!',
},
],
},
];
export default function BasicEditor() {
return (
<Plate initialValue={initialValue}>
<Editor />
</Plate>
);
}
Note: Plate uses the type
property to enable plugins to render nodes
by type.
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.
// ...
export default function BasicEditor() {
return (
<Plate
initialValue={initialValue}
onChange={(newValue) => {
// save newValue...
}}
>
<Editor />
</Plate>
);
}
Plugins
Use our interactive builder to pick your plugins.
Let's use the basic plugins for a rich-text editor.
The debug value indicates everything works as expected. However, we haven't provided any components for rendering, hence it's utilizing the default (unstyled) ones. The default element component is a div
and the default leaf component is a span
.
Note: You don't need to add core plugins such as react
and
history
as Plate
already does it.
Components
Note: Plate plugins are packaged unstyled, implying that you have complete control over markup and styling, hence you can integrate your own design system or Plate UI. If using the latter, use our interactive builder to pick your components.
To simultaneously plug-in all the components, createPlugins
can be used:
- use the first parameter for the
plugins
- use the second parameter for the
components
. In the following example, we have createdcreatePlateUI
, which returns all Plate components by plugin key.
// ...
import {
createBoldPlugin,
createCodePlugin,
createItalicPlugin,
createStrikethroughPlugin,
createUnderlinePlugin,
} from '@udecode/plate-basic-marks';
import { createBlockquotePlugin } from '@udecode/plate-block-quote';
import { createPlugins, Plate } from '@udecode/plate-common';
import { createHeadingPlugin } from '@udecode/plate-heading';
import { createParagraphPlugin } from '@udecode/plate-paragraph';
import { createPlateUI } from '@/lib/create-plate-ui';
const plugins = createPlugins(
[
createParagraphPlugin(),
createBlockquotePlugin(),
createHeadingPlugin(),
createBoldPlugin(),
createItalicPlugin(),
createUnderlinePlugin(),
createStrikethroughPlugin(),
createCodePlugin(),
],
{
components: createPlateUI(),
}
);
export default function BasicEditor() {
return (
<Plate
plugins={plugins}
// ...
>
<Editor />
</Plate>
);
}
import { withProps } from '@udecode/cn';
import {
MARK_BOLD,
MARK_CODE,
MARK_ITALIC,
MARK_STRIKETHROUGH,
MARK_SUBSCRIPT,
MARK_SUPERSCRIPT,
MARK_UNDERLINE,
} from '@udecode/plate-basic-marks';
import { ELEMENT_BLOCKQUOTE } from '@udecode/plate-block-quote';
import {
ELEMENT_CODE_BLOCK,
ELEMENT_CODE_LINE,
ELEMENT_CODE_SYNTAX,
} from '@udecode/plate-code-block';
import { MARK_COMMENT } from '@udecode/plate-comments';
import {
PlateElement,
PlateLeaf,
type PlatePluginComponent,
} from '@udecode/plate-common';
import { ELEMENT_INLINE_DATE } from '@udecode/plate-date';
import { ELEMENT_EXCALIDRAW } from '@udecode/plate-excalidraw';
import { MARK_SEARCH_HIGHLIGHT } from '@udecode/plate-find-replace';
import {
ELEMENT_H1,
ELEMENT_H2,
ELEMENT_H3,
ELEMENT_H4,
ELEMENT_H5,
ELEMENT_H6,
} from '@udecode/plate-heading';
import { MARK_HIGHLIGHT } from '@udecode/plate-highlight';
import { ELEMENT_HR } from '@udecode/plate-horizontal-rule';
import { MARK_KBD } from '@udecode/plate-kbd';
import { ELEMENT_COLUMN, ELEMENT_COLUMN_GROUP } from '@udecode/plate-layout';
import { ELEMENT_LINK } from '@udecode/plate-link';
import {
ELEMENT_LI,
ELEMENT_OL,
ELEMENT_TODO_LI,
ELEMENT_UL,
} from '@udecode/plate-list';
import { ELEMENT_IMAGE, ELEMENT_MEDIA_EMBED } from '@udecode/plate-media';
import { ELEMENT_MENTION, ELEMENT_MENTION_INPUT } from '@udecode/plate-mention';
import { ELEMENT_PARAGRAPH } from '@udecode/plate-paragraph';
import { ELEMENT_SLASH_INPUT } from '@udecode/plate-slash-command';
import {
ELEMENT_TABLE,
ELEMENT_TD,
ELEMENT_TH,
ELEMENT_TR,
} from '@udecode/plate-table';
import { ELEMENT_TOGGLE } from '@udecode/plate-toggle';
import { BlockquoteElement } from '@/registry/default/plate-ui/blockquote-element';
import { CodeBlockElement } from '@/registry/default/plate-ui/code-block-element';
import { CodeLeaf } from '@/registry/default/plate-ui/code-leaf';
import { CodeLineElement } from '@/registry/default/plate-ui/code-line-element';
import { CodeSyntaxLeaf } from '@/registry/default/plate-ui/code-syntax-leaf';
import { ColumnElement } from '@/registry/default/plate-ui/column-element';
import { ColumnGroupElement } from '@/registry/default/plate-ui/column-group-element';
import { CommentLeaf } from '@/registry/default/plate-ui/comment-leaf';
import { EmojiInputElement } from '@/registry/default/plate-ui/emoji-input-element';
import { ExcalidrawElement } from '@/registry/default/plate-ui/excalidraw-element';
import { HeadingElement } from '@/registry/default/plate-ui/heading-element';
import { HighlightLeaf } from '@/registry/default/plate-ui/highlight-leaf';
import { HrElement } from '@/registry/default/plate-ui/hr-element';
import { ImageElement } from '@/registry/default/plate-ui/image-element';
import { InlineDateElement } from '@/registry/default/plate-ui/inline-date-element';
import { KbdLeaf } from '@/registry/default/plate-ui/kbd-leaf';
import { LinkElement } from '@/registry/default/plate-ui/link-element';
import { ListElement } from '@/registry/default/plate-ui/list-element';
import { MediaEmbedElement } from '@/registry/default/plate-ui/media-embed-element';
import { MentionElement } from '@/registry/default/plate-ui/mention-element';
import { MentionInputElement } from '@/registry/default/plate-ui/mention-input-element';
import { ParagraphElement } from '@/registry/default/plate-ui/paragraph-element';
import { withPlaceholders } from '@/registry/default/plate-ui/placeholder';
import { SearchHighlightLeaf } from '@/registry/default/plate-ui/search-highlight-leaf';
import { SlashInputElement } from '@/registry/default/plate-ui/slash-input-element';
import {
TableCellElement,
TableCellHeaderElement,
} from '@/registry/default/plate-ui/table-cell-element';
import { TableElement } from '@/registry/default/plate-ui/table-element';
import { TableRowElement } from '@/registry/default/plate-ui/table-row-element';
import { TodoListElement } from '@/registry/default/plate-ui/todo-list-element';
import { ToggleElement } from '@/registry/default/plate-ui/toggle-element';
import { withDraggables } from '@/registry/default/plate-ui/with-draggables';
import { ELEMENT_EMOJI_INPUT } from '../../../../../packages/emoji/dist';
export const createPlateUI = (
overrideByKey?: Partial<Record<string, PlatePluginComponent>>,
{
draggable,
placeholder,
}: { draggable?: boolean; placeholder?: boolean } = {}
) => {
let components: Record<string, PlatePluginComponent> = {
[ELEMENT_BLOCKQUOTE]: BlockquoteElement,
[ELEMENT_CODE_BLOCK]: CodeBlockElement,
[ELEMENT_CODE_LINE]: CodeLineElement,
[ELEMENT_CODE_SYNTAX]: CodeSyntaxLeaf,
[ELEMENT_COLUMN]: ColumnElement,
[ELEMENT_COLUMN_GROUP]: ColumnGroupElement,
[ELEMENT_EMOJI_INPUT]: EmojiInputElement,
[ELEMENT_EXCALIDRAW]: ExcalidrawElement,
[ELEMENT_H1]: withProps(HeadingElement, { variant: 'h1' }),
[ELEMENT_H2]: withProps(HeadingElement, { variant: 'h2' }),
[ELEMENT_H3]: withProps(HeadingElement, { variant: 'h3' }),
[ELEMENT_H4]: withProps(HeadingElement, { variant: 'h4' }),
[ELEMENT_H5]: withProps(HeadingElement, { variant: 'h5' }),
[ELEMENT_H6]: withProps(HeadingElement, { variant: 'h6' }),
[ELEMENT_HR]: HrElement,
[ELEMENT_IMAGE]: ImageElement,
[ELEMENT_INLINE_DATE]: InlineDateElement,
[ELEMENT_LI]: withProps(PlateElement, { as: 'li' }),
[ELEMENT_LINK]: LinkElement,
[ELEMENT_MEDIA_EMBED]: MediaEmbedElement,
[ELEMENT_MENTION]: MentionElement,
[ELEMENT_MENTION_INPUT]: MentionInputElement,
[ELEMENT_OL]: withProps(ListElement, { variant: 'ol' }),
[ELEMENT_PARAGRAPH]: ParagraphElement,
[ELEMENT_SLASH_INPUT]: SlashInputElement,
[ELEMENT_TABLE]: TableElement,
[ELEMENT_TD]: TableCellElement,
[ELEMENT_TH]: TableCellHeaderElement,
[ELEMENT_TODO_LI]: TodoListElement,
[ELEMENT_TOGGLE]: ToggleElement,
[ELEMENT_TR]: TableRowElement,
[ELEMENT_UL]: withProps(ListElement, { variant: 'ul' }),
[MARK_BOLD]: withProps(PlateLeaf, { as: 'strong' }),
[MARK_CODE]: CodeLeaf,
[MARK_COMMENT]: CommentLeaf,
[MARK_HIGHLIGHT]: HighlightLeaf,
[MARK_ITALIC]: withProps(PlateLeaf, { as: 'em' }),
[MARK_KBD]: KbdLeaf,
[MARK_SEARCH_HIGHLIGHT]: SearchHighlightLeaf,
[MARK_STRIKETHROUGH]: withProps(PlateLeaf, { as: 's' }),
[MARK_SUBSCRIPT]: withProps(PlateLeaf, { as: 'sub' }),
[MARK_SUPERSCRIPT]: withProps(PlateLeaf, { as: 'sup' }),
[MARK_UNDERLINE]: withProps(PlateLeaf, { as: 'u' }),
};
if (overrideByKey) {
Object.keys(overrideByKey).forEach((key) => {
(components as any)[key] = (overrideByKey as any)[key];
});
}
if (placeholder) {
components = withPlaceholders(components);
}
if (draggable) {
components = withDraggables(components);
}
return components;
};
🌳 Blocks
Create blockquotes to emphasize important information or highlight quotes from external sources.
// Use code blocks to showcase code snippetsfunction greet() { console.info('Hello World!');}
🌱 Marks
code
formatting for easy readability.That's it!
You can now play around with the Playground and start building your own editor.