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.
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.