Block types change whole elements. Marks change text. Use marks for inline formatting such as bold, italic, code, and strikethrough.
Starting Point
Start with the block renderer from the previous walkthrough:
const renderElement = (props) => {
switch (props.element.type) {
case "code":
return <CodeElement {...props} />;
default:
return <DefaultElement {...props} />;
}
};const renderElement = (props) => {
switch (props.element.type) {
case "code":
return <CodeElement {...props} />;
default:
return <DefaultElement {...props} />;
}
};Toggle A Mark
Use editor.update.marks.toggle(...) for a single formatting command.
import { ElementApi } from "@platejs/plite";
const App = () => {
const editor = usePliteEditor({ initialValue });
return (
<Plite editor={editor}>
<Editable
renderElement={renderElement}
onKeyDown={(event) => {
if (!event.ctrlKey) {
return;
}
switch (event.key) {
case "`": {
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),
}
);
});
break;
}
case "b": {
event.preventDefault();
editor.update.marks.toggle("bold");
break;
}
}
}}
/>
</Plite>
);
};import { ElementApi } from "@platejs/plite";
const App = () => {
const editor = usePliteEditor({ initialValue });
return (
<Plite editor={editor}>
<Editable
renderElement={renderElement}
onKeyDown={(event) => {
if (!event.ctrlKey) {
return;
}
switch (event.key) {
case "`": {
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),
}
);
});
break;
}
case "b": {
event.preventDefault();
editor.update.marks.toggle("bold");
break;
}
}
}}
/>
</Plite>
);
};editor.update.marks.toggle('bold') applies the mark to the selected range.
When the selection is collapsed, it changes the active mark for text typed next.
Mutually exclusive marks use clear:
editor.update.marks.toggle("subscript", true, { clear: "superscript" });editor.update.marks.toggle("subscript", true, { clear: "superscript" });Render Marks
Add renderLeaf to decide how marked text appears.
const renderLeaf = ({ attributes, children, leaf }) => {
return (
<span
{...attributes}
style={{
fontWeight: leaf.bold ? "bold" : "normal",
}}
>
{children}
</span>
);
};const renderLeaf = ({ attributes, children, leaf }) => {
return (
<span
{...attributes}
style={{
fontWeight: leaf.bold ? "bold" : "normal",
}}
>
{children}
</span>
);
};Pass both renderers to Editable:
const App = () => {
const editor = usePliteEditor({ initialValue });
return (
<Plite editor={editor}>
<Editable
renderElement={renderElement}
renderLeaf={renderLeaf}
onKeyDown={(event) => {
if (!event.ctrlKey) {
return;
}
switch (event.key) {
case "`": {
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),
}
);
});
break;
}
case "b": {
event.preventDefault();
editor.update.marks.toggle("bold");
break;
}
}
}}
/>
</Plite>
);
};const App = () => {
const editor = usePliteEditor({ initialValue });
return (
<Plite editor={editor}>
<Editable
renderElement={renderElement}
renderLeaf={renderLeaf}
onKeyDown={(event) => {
if (!event.ctrlKey) {
return;
}
switch (event.key) {
case "`": {
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),
}
);
});
break;
}
case "b": {
event.preventDefault();
editor.update.marks.toggle("bold");
break;
}
}
}}
/>
</Plite>
);
};Hotkeys like Ctrl+B belong in onKeyDown because they are UI shortcuts.
Editing behavior that maps to Plite transform names, such as deleteBackward,
deleteForward, insertBreak, or insertText, belongs in extension
transforms so keyboard input, native input, toolbars, programmatic calls, and
tests use the same behavior.