Block types change whole elements. Marks change text. Use marks for inline formatting such as bold, italic, code, and strikethrough.
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} />;
}
};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.blocks.set({ type: match ? "paragraph" : "code" });
});
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
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.
Declare mutually exclusive marks in the schema:
const ScriptPosition = schema.property.exclusive("app:script-position");
const formattingProperties = [
schema.textProperty("subscript", property.boolean(), {
exclusive: [ScriptPosition],
}),
schema.textProperty("superscript", property.boolean(), {
exclusive: [ScriptPosition],
}),
];
editor.update.marks.toggle("subscript");const ScriptPosition = schema.property.exclusive("app:script-position");
const formattingProperties = [
schema.textProperty("subscript", property.boolean(), {
exclusive: [ScriptPosition],
}),
schema.textProperty("superscript", property.boolean(), {
exclusive: [ScriptPosition],
}),
];
editor.update.marks.toggle("subscript");Enabling one group member removes the active peer automatically.
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.blocks.set({ type: match ? "paragraph" : "code" });
});
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.
Hotkeys like Ctrl+B belong in onKeyDown because they are UI shortcuts.
Reusable editing behavior such as deleting, inserting breaks, or inserting text
belongs in extension commands so keyboard input, native input, toolbars,
programmatic calls, and tests use the same behavior.