The smallest editor can render a paragraph without a custom renderer, but real editors usually need block types such as paragraphs, quotes, code blocks, list items, cards, and embeds.
Starting Point
Start from the editor from the previous walkthrough:
const initialValue = [
{
type: "paragraph",
children: [{ text: "A line of text in a paragraph." }],
},
];
const App = () => {
const editor = useSlateEditor({ initialValue });
return (
<Slate editor={editor}>
<Editable
onKeyDown={(event) => {
if (event.key === "&") {
event.preventDefault();
editor.update((tx) => {
tx.text.insert("and");
});
}
}}
/>
</Slate>
);
};const initialValue = [
{
type: "paragraph",
children: [{ text: "A line of text in a paragraph." }],
},
];
const App = () => {
const editor = useSlateEditor({ initialValue });
return (
<Slate editor={editor}>
<Editable
onKeyDown={(event) => {
if (event.key === "&") {
event.preventDefault();
editor.update((tx) => {
tx.text.insert("and");
});
}
}}
/>
</Slate>
);
};Render Elements
Element renderers are normal React functions. Always spread attributes on the
top-level DOM element and render children.
const CodeElement = ({ attributes, children }) => {
return (
<pre {...attributes}>
<code>{children}</code>
</pre>
);
};
const DefaultElement = ({ attributes, children }) => {
return <p {...attributes}>{children}</p>;
};
const renderElement = (props) => {
switch (props.element.type) {
case "code":
return <CodeElement {...props} />;
default:
return <DefaultElement {...props} />;
}
};const CodeElement = ({ attributes, children }) => {
return (
<pre {...attributes}>
<code>{children}</code>
</pre>
);
};
const DefaultElement = ({ attributes, children }) => {
return <p {...attributes}>{children}</p>;
};
const renderElement = (props) => {
switch (props.element.type) {
case "code":
return <CodeElement {...props} />;
default:
return <DefaultElement {...props} />;
}
};Pass the renderer to Editable:
const App = () => {
const editor = useSlateEditor({ initialValue });
return (
<Slate editor={editor}>
<Editable
renderElement={renderElement}
onKeyDown={(event) => {
if (event.key === "&") {
event.preventDefault();
editor.update((tx) => {
tx.text.insert("and");
});
}
}}
/>
</Slate>
);
};const App = () => {
const editor = useSlateEditor({ initialValue });
return (
<Slate editor={editor}>
<Editable
renderElement={renderElement}
onKeyDown={(event) => {
if (event.key === "&") {
event.preventDefault();
editor.update((tx) => {
tx.text.insert("and");
});
}
}}
/>
</Slate>
);
};Keep renderer functions stable by defining them at module scope or memoizing them once.
Toggle A Block Type
Use editor.update(...) and tx.nodes.set(...) to change the selected block.
import { ElementApi } from "@platejs/slate";
const App = () => {
const editor = useSlateEditor({ initialValue });
return (
<Slate editor={editor}>
<Editable
renderElement={renderElement}
onKeyDown={(event) => {
if (event.key === "`" && event.ctrlKey) {
event.preventDefault();
editor.update((tx) => {
tx.nodes.set(
{ type: "code" },
{
match: (node) =>
ElementApi.isElement(node) && tx.schema.isBlock(node),
}
);
});
}
}}
/>
</Slate>
);
};import { ElementApi } from "@platejs/slate";
const App = () => {
const editor = useSlateEditor({ initialValue });
return (
<Slate editor={editor}>
<Editable
renderElement={renderElement}
onKeyDown={(event) => {
if (event.key === "`" && event.ctrlKey) {
event.preventDefault();
editor.update((tx) => {
tx.nodes.set(
{ type: "code" },
{
match: (node) =>
ElementApi.isElement(node) && tx.schema.isBlock(node),
}
);
});
}
}}
/>
</Slate>
);
};To make the shortcut toggle, read first, then write:
import { ElementApi } from "@platejs/slate";
const App = () => {
const editor = useSlateEditor({ initialValue });
return (
<Slate editor={editor}>
<Editable
renderElement={renderElement}
onKeyDown={(event) => {
if (event.key === "`" && event.ctrlKey) {
event.preventDefault();
const match = editor.read((state) =>
state.nodes.find({
match: (node) =>
ElementApi.isElement(node) && node.type === "code",
})
);
editor.update((tx) => {
tx.nodes.set(
{ type: match ? "paragraph" : "code" },
{
match: (node) =>
ElementApi.isElement(node) && tx.schema.isBlock(node),
}
);
});
}
}}
/>
</Slate>
);
};import { ElementApi } from "@platejs/slate";
const App = () => {
const editor = useSlateEditor({ initialValue });
return (
<Slate editor={editor}>
<Editable
renderElement={renderElement}
onKeyDown={(event) => {
if (event.key === "`" && event.ctrlKey) {
event.preventDefault();
const match = editor.read((state) =>
state.nodes.find({
match: (node) =>
ElementApi.isElement(node) && node.type === "code",
})
);
editor.update((tx) => {
tx.nodes.set(
{ type: match ? "paragraph" : "code" },
{
match: (node) =>
ElementApi.isElement(node) && tx.schema.isBlock(node),
}
);
});
}
}}
/>
</Slate>
);
};The renderer controls how a node looks. The transaction controls the document shape.