This guide walks you through setting up Plate from scratch, giving you full control over styling and component rendering. This approach is ideal if you're not using a UI library like shadcn/ui or Tailwind CSS.
Install platejs for the core editor runtime and React components.
Plate provides ESM packages. If you're using TypeScript, ensure your tsconfig.json is configured correctly. The recommended setup for Plate requires TypeScript 5.0+ with the "moduleResolution": "bundler" setting:
// tsconfig.json
{
"compilerOptions": {
// ... other options
"module": "esnext", // or commonjs if your setup requires it and handles ESM interop
"moduleResolution": "bundler",
// ... other options
},
}// tsconfig.json
{
"compilerOptions": {
// ... other options
"module": "esnext", // or commonjs if your setup requires it and handles ESM interop
"moduleResolution": "bundler",
// ... other options
},
}For Node.js module resolution and import troubleshooting, see the TypeScript guide.
Start by creating a basic editor component. This example sets up a simple editor.
import React from 'react';
import type { Value } from 'platejs';
import { EditorRoot, EditorContent, useCreateEditor } from 'platejs/react';
export default function App() {
const editor = useCreateEditor();
return (
<EditorRoot editor={editor}>
<EditorContent
style={{ padding: '16px 64px', minHeight: '100px' }}
placeholder="Type your amazing content here..."
/>
</EditorRoot>
);
}import React from 'react';
import type { Value } from 'platejs';
import { EditorRoot, EditorContent, useCreateEditor } from 'platejs/react';
export default function App() {
const editor = useCreateEditor();
return (
<EditorRoot editor={editor}>
<EditorContent
style={{ padding: '16px 64px', minHeight: '100px' }}
placeholder="Type your amazing content here..."
/>
</EditorRoot>
);
}useCreateEditor creates a memoized editor instance, ensuring stability across re-renders. For a non-memoized version, use createEditor from platejs/react.
At this point, you'll have a very basic editor capable of displaying and editing plain text.
Let's add support for basic text formatting like bold, italic, and underline.
The mark plugins are included in the platejs installation:
import React from 'react';
import type { Value } from 'platejs';
import {
BoldPlugin,
ItalicPlugin,
UnderlinePlugin,
} from 'platejs/react';
import {
EditorRoot,
EditorContent,
useCreateEditor,
} from 'platejs/react';
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],
initialValue,
});
return (
<EditorRoot editor={editor}>
{/* You would typically add a toolbar here to toggle marks */}
<EditorContent style={{ padding: '16px 64px', minHeight: '100px' }} />
</EditorRoot>
);
}import React from 'react';
import type { Value } from 'platejs';
import {
BoldPlugin,
ItalicPlugin,
UnderlinePlugin,
} from 'platejs/react';
import {
EditorRoot,
EditorContent,
useCreateEditor,
} from 'platejs/react';
const initialValue: Value = [
{
type: 'paragraph',
children: [
{ text: 'Hello! Try out the ' },
{ text: 'bold', bold: true
Mark plugins like BoldPlugin, ItalicPlugin, and UnderlinePlugin come with default components that render as <strong>, <em>, and <u> elements respectively. You don't need to register custom components unless you want to customize their appearance.
You'll need to implement your own toolbar to apply these marks. For example, to toggle bold: editor.update.bold.toggle().
Now, let's add support for block-level elements like headings, and blockquotes.
import React from 'react';
import type { Value } from 'platejs';
import {
BlockquotePlugin,
BoldPlugin,
HeadingPlugin,
ItalicPlugin,
UnderlinePlugin,
} from 'platejs/react';
import {
EditorRoot,
EditorContent,
EditorElement,
useCreateEditor,
type EditorElementProps,
} from 'platejs/react';
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',
},
];
// Define element components
function HeadingElement(props: EditorElementProps<typeof HeadingPlugin>) {
const tag = `h${props.element.level}` as const;
return <EditorElement as={tag} {...props} />;
}
function BlockquoteElement(props: EditorElementProps<typeof BlockquotePlugin>) {
return (
<EditorElement
as="blockquote"
style={{
borderLeft: '2px solid #eee',
marginLeft: 0,
marginRight: 0,
paddingLeft: '24px',
color: '#666',
fontStyle: 'italic',
}}
{...props}
/>
);
}
export default function App() {
const editor = useCreateEditor({
plugins: [
BoldPlugin,
ItalicPlugin,
UnderlinePlugin,
HeadingPlugin.configure({ component: HeadingElement }),
BlockquotePlugin.configure({ component: BlockquoteElement }),
],
initialValue,
});
return (
<EditorRoot editor={editor}>
{/* You would typically add a toolbar here to toggle elements and marks */}
<EditorContent style={{ padding: '16px 64px', minHeight: '100px' }} />
</EditorRoot>
);
}import React from 'react';
import type { Value } from 'platejs';
import {
BlockquotePlugin,
BoldPlugin,
HeadingPlugin,
ItalicPlugin,
UnderlinePlugin,
} from 'platejs/react';
import {
EditorRoot,
EditorContent,
EditorElement,
useCreateEditor,
type EditorElementProps,
} from 'platejs/react';
const initialValue: Value = [
{
children: [{ text:
Notice how we use Plugin.configure({ component: Component }) to register components with block element plugins like headings and blockquotes. This is the recommended approach for associating React components with Plate plugins when you need custom styling or behavior.
To make the editor's content persistent, let's integrate state management to save and load the editor's value.
import React from 'react';
import type { Value } from 'platejs';
import {
BlockquotePlugin,
BoldPlugin,
HeadingPlugin,
ItalicPlugin,
UnderlinePlugin,
} from 'platejs/react';
import {
EditorRoot,
EditorContent,
EditorElement,
useCreateEditor,
type EditorElementProps,
} from 'platejs/react';
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',
},
];
// Define element components
function HeadingElement(props: EditorElementProps<typeof HeadingPlugin>) {
const tag = `h${props.element.level}` as const;
return <EditorElement as={tag} {...props} />;
}
function BlockquoteElement(props: EditorElementProps<typeof BlockquotePlugin>) {
return (
<EditorElement
as="blockquote"
style={{
borderLeft: '2px solid #eee',
marginLeft: 0,
marginRight: 0,
paddingLeft: '24px',
color: '#666',
fontStyle: 'italic',
}}
{...props}
/>
);
}
export default function App() {
const editor = useCreateEditor({
plugins: [
BoldPlugin,
ItalicPlugin,
UnderlinePlugin,
HeadingPlugin.configure({ component: HeadingElement }),
BlockquotePlugin.configure({ component: BlockquoteElement }),
],
initialValue: () => {
const savedValue = localStorage.getItem('plate-manual-demo');
return savedValue ? JSON.parse(savedValue) : initialValue;
},
});
return (
<EditorRoot
editor={editor}
onValueChange={({ value }) => {
localStorage.setItem('plate-manual-demo', JSON.stringify(value));
}}
>
{/* Toolbar would go here */}
<div style={{ padding: '8px 0' }}>
<button
onClick={() => {
editor.update((tx) => {
tx.value.replace({ children: initialValue });
});
}}
style={{
padding: '4px 8px',
margin: '0 4px',
border: '1px solid #ccc',
borderRadius: '4px',
cursor: 'pointer',
}}
>
Reset
</button>
</div>
<EditorContent
style={{
padding: '16px 64px',
minHeight: '100px',
border: '1px solid #eee',
borderRadius: '4px',
}}
placeholder="Type your amazing content here..."
/>
</EditorRoot>
);
}import React from 'react';
import type { Value } from 'platejs';
import {
BlockquotePlugin,
BoldPlugin,
HeadingPlugin,
ItalicPlugin,
UnderlinePlugin,
} from 'platejs/react';
import {
EditorRoot,
EditorContent,
EditorElement,
useCreateEditor,
type EditorElementProps,
} from 'platejs/react';
const initialValue: Value = [
{
children: [{ text: 'Title'
The example above demonstrates a basic pattern for managing editor value:
initialValue option in useCreateEditoronValueChange prop on <EditorRoot>editor.update(...) with tx.value.replace(...) to
restore the initial valueYou've now set up a basic Plate editor manually! From here, you can:
platejs) and add them to your plugins array.editor.update.bold.toggle(), editor.plugin(HeadingPlugin).update.toggle({ level: 1 })). You can also use the editor state with the Editor API.