Getting Started
A quick tutorial to get you up and running with Plate.
Create project
Using Plate components? Check out the installation guide.
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 leveraging Plate
and editableProps
.
import { Plate } from '@udecode/plate-common';
const editableProps = {
placeholder: 'Type...',
};
export default function BasicEditor() {
return <Plate editableProps={editableProps} />;
}
Simple and straightforward. Try it out:
Note: The following examples are using these global styles:
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%;
--card: 0 0% 100%;
--card-foreground: 240 10% 3.9%;
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--primary: 240 5.9% 10%;
--primary-foreground: 0 0% 98%;
--secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--ring: 240 5% 64.9%;
--radius: 0.5rem;
}
.dark {
--background: 240 10% 3.9%;
--foreground: 0 0% 98%;
--muted: 240 3.7% 15.9%;
--muted-foreground: 240 5% 64.9%;
--popover: 240 10% 3.9%;
--popover-foreground: 0 0% 98%;
--card: 240 10% 3.9%;
--card-foreground: 0 0% 98%;
--border: 240 3.7% 25%;
--input: 240 3.7% 25%;
--primary: 0 0% 98%;
--primary-foreground: 240 5.9% 10%;
--secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%;
--accent: 240 3.7% 15.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 85.7% 97.3%;
--ring: 240 3.7% 25%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
font-feature-settings: 'rlig' 1, 'calt' 1;
}
kbd {
font-size: inherit;
}
[data-slate-editor] {
font-family: Inter, ui-sans-serif, system-ui, -apple-system,
BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans,
sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', Segoe UI Symbol,
'Noto Color Emoji';
}
.slate-selection-area {
background: #4f90f22d;
border: 1000px solid #4f90f2;
}
}
@layer utilities {
.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);
}
}
@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 editableProps={editableProps} initialValue={initialValue} />;
}
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
editableProps={editableProps}
initialValue={initialValue}
onChange={(newValue) => {
// save newValue...
}}
/>
);
}
null
Plugins
Let's use the basic plugins for a rich-text editor.
null
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.
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'll callcreatePlateUI()
, 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 { Plate, createPlugins } 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}
// ...
/>
);
}
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,
PlatePluginComponent,
withProps,
} from '@udecode/plate-common';
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_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_TABLE,
ELEMENT_TD,
ELEMENT_TH,
ELEMENT_TR,
} from '@udecode/plate-table';
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 { CommentLeaf } from '@/registry/default/plate-ui/comment-leaf';
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 { 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 {
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 { withDraggables } from '@/registry/default/plate-ui/with-draggables';
export const createPlateUI = (
overrideByKey?: Partial<Record<string, PlatePluginComponent>>,
{
draggable,
placeholder,
}: { placeholder?: boolean; draggable?: boolean } = {}
) => {
let components: Record<string, PlatePluginComponent> = {
[ELEMENT_BLOCKQUOTE]: BlockquoteElement,
[ELEMENT_CODE_BLOCK]: CodeBlockElement,
[ELEMENT_CODE_LINE]: CodeLineElement,
[ELEMENT_CODE_SYNTAX]: CodeSyntaxLeaf,
[ELEMENT_HR]: HrElement,
[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_IMAGE]: ImageElement,
[ELEMENT_LI]: withProps(PlateElement, { as: 'li' }),
[ELEMENT_LINK]: LinkElement,
[ELEMENT_MEDIA_EMBED]: MediaEmbedElement,
[ELEMENT_MENTION]: MentionElement,
[ELEMENT_MENTION_INPUT]: MentionInputElement,
[ELEMENT_UL]: withProps(ListElement, { variant: 'ul' }),
[ELEMENT_OL]: withProps(ListElement, { variant: 'ol' }),
[ELEMENT_PARAGRAPH]: ParagraphElement,
[ELEMENT_TABLE]: TableElement,
[ELEMENT_TD]: TableCellElement,
[ELEMENT_TH]: TableCellHeaderElement,
[ELEMENT_TODO_LI]: TodoListElement,
[ELEMENT_TR]: TableRowElement,
[ELEMENT_EXCALIDRAW]: ExcalidrawElement,
[MARK_BOLD]: withProps(PlateLeaf, { as: 'strong' }),
[MARK_CODE]: CodeLeaf,
[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' }),
[MARK_COMMENT]: CommentLeaf,
};
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.