React components and hooks for building customizable rich text editors using the Slate framework.
—
Customizable render functions for controlling how editor content is displayed. These functions provide complete control over the visual representation of editor content and are essential for creating custom editor UI.
Custom function for rendering Slate elements as React components.
/**
* Custom element renderer props passed to renderElement
*/
interface RenderElementProps {
/** React children to render inside the element */
children: any;
/** The Slate element being rendered */
element: Element;
/** Required DOM attributes for proper Slate functionality */
attributes: {
'data-slate-node': 'element';
'data-slate-inline'?: true;
'data-slate-void'?: true;
dir?: 'rtl';
ref: any;
};
}
/**
* Type for custom element render function
* @param props - Render element props
* @returns JSX element representing the rendered element
*/
type RenderElementFunction = (props: RenderElementProps) => JSX.Element;Usage Example:
import React from 'react';
import { Editable, RenderElementProps } from 'slate-react';
const renderElement = ({ attributes, children, element }: RenderElementProps) => {
switch (element.type) {
case 'paragraph':
return <p {...attributes}>{children}</p>;
case 'heading-one':
return <h1 {...attributes}>{children}</h1>;
case 'heading-two':
return <h2 {...attributes}>{children}</h2>;
case 'block-quote':
return <blockquote {...attributes}>{children}</blockquote>;
case 'list-item':
return <li {...attributes}>{children}</li>;
case 'bulleted-list':
return <ul {...attributes}>{children}</ul>;
case 'numbered-list':
return <ol {...attributes}>{children}</ol>;
case 'link':
return (
<a {...attributes} href={element.url}>
{children}
</a>
);
default:
return <div {...attributes}>{children}</div>;
}
};
const MyEditor = () => (
<Editable renderElement={renderElement} />
);Custom function for rendering Slate text leaves with formatting.
/**
* Custom leaf renderer props passed to renderLeaf
*/
interface RenderLeafProps {
/** React children to render inside the leaf */
children: any;
/** The Slate leaf being rendered (same as text) */
leaf: Text;
/** The Slate text node being rendered */
text: Text;
/** Required DOM attributes for proper Slate functionality */
attributes: {
'data-slate-leaf': true;
ref: any;
};
/** Position information for the leaf (optional) */
leafPosition?: LeafPosition;
}
/**
* Type for custom leaf render function
* @param props - Render leaf props
* @returns JSX element representing the rendered leaf
*/
type RenderLeafFunction = (props: RenderLeafProps) => JSX.Element;Usage Example:
import React from 'react';
import { Editable, RenderLeafProps } from 'slate-react';
const renderLeaf = ({ attributes, children, leaf }: RenderLeafProps) => {
if (leaf.bold) {
children = <strong>{children}</strong>;
}
if (leaf.italic) {
children = <em>{children}</em>;
}
if (leaf.underline) {
children = <u>{children}</u>;
}
if (leaf.strikethrough) {
children = <del>{children}</del>;
}
if (leaf.code) {
children = <code>{children}</code>;
}
if (leaf.highlight) {
children = (
<span style={{ backgroundColor: 'yellow' }}>
{children}
</span>
);
}
return <span {...attributes}>{children}</span>;
};
const MyEditor = () => (
<Editable renderLeaf={renderLeaf} />
);Custom function for rendering individual text nodes. Rarely needed unless you need very specific text handling.
/**
* Custom text renderer props passed to renderText
*/
interface RenderTextProps {
/** The Slate text node being rendered */
text: Text;
/** React children to render */
children: any;
/** Required DOM attributes for proper Slate functionality */
attributes: {
'data-slate-text': true;
ref: any;
};
}
/**
* Type for custom text render function
* @param props - Render text props
* @returns JSX element representing the rendered text
*/
type RenderTextFunction = (props: RenderTextProps) => JSX.Element;Usage Example:
import React from 'react';
import { Editable, RenderTextProps } from 'slate-react';
const renderText = ({ attributes, children, text }: RenderTextProps) => {
// Custom text processing - e.g., syntax highlighting
if (text.text.includes('TODO:')) {
return (
<span {...attributes} style={{ color: 'red', fontWeight: 'bold' }}>
{children}
</span>
);
}
return <span {...attributes}>{children}</span>;
};
const MyEditor = () => (
<Editable renderText={renderText} />
);Custom function for rendering the editor placeholder when it's empty.
/**
* Custom placeholder renderer props passed to renderPlaceholder
*/
interface RenderPlaceholderProps {
/** React children to render (the placeholder text) */
children: any;
/** Required DOM attributes for proper Slate functionality */
attributes: {
'data-slate-placeholder': boolean;
dir?: 'rtl';
contentEditable: boolean;
ref: React.RefCallback<any>;
style: React.CSSProperties;
};
}
/**
* Type for custom placeholder render function
* @param props - Render placeholder props
* @returns JSX element representing the rendered placeholder
*/
type RenderPlaceholderFunction = (props: RenderPlaceholderProps) => JSX.Element;Usage Example:
import React from 'react';
import { Editable, RenderPlaceholderProps } from 'slate-react';
const renderPlaceholder = ({ attributes, children }: RenderPlaceholderProps) => (
<div
{...attributes}
style={{
...attributes.style,
color: '#aaa',
fontStyle: 'italic',
fontSize: '14px'
}}
>
{children}
</div>
);
const MyEditor = () => (
<Editable
placeholder="Type something amazing..."
renderPlaceholder={renderPlaceholder}
/>
);Custom function for rendering chunks in performance-optimized scenarios with large documents.
/**
* Custom chunk renderer props passed to renderChunk
*/
interface RenderChunkProps {
/** Whether this is the highest-level chunk */
highest: boolean;
/** Whether this is the lowest-level chunk */
lowest: boolean;
/** React children to render inside the chunk */
children: any;
/** Required DOM attributes for proper Slate functionality */
attributes: {
'data-slate-chunk': true;
ref: any;
};
}
/**
* Type for custom chunk render function
* @param props - Render chunk props
* @returns JSX element representing the rendered chunk
*/
type RenderChunkFunction = (props: RenderChunkProps) => JSX.Element;Usage Example:
import React from 'react';
import { Editable, RenderChunkProps } from 'slate-react';
const renderChunk = ({ attributes, children, highest, lowest }: RenderChunkProps) => {
// Apply different styling based on chunk level
const className = highest ? 'chunk-highest' : lowest ? 'chunk-lowest' : 'chunk-middle';
return (
<div {...attributes} className={className}>
{children}
</div>
);
};
const LargeDocumentEditor = () => (
<Editable renderChunk={renderChunk} />
);Render different components based on element properties or editor state:
import React from 'react';
import { useSelected, useFocused, RenderElementProps } from 'slate-react';
const ConditionalElement = ({ attributes, children, element }: RenderElementProps) => {
const selected = useSelected();
const focused = useFocused();
const isActive = selected && focused;
switch (element.type) {
case 'image':
return (
<div {...attributes}>
<img
src={element.url}
alt={element.alt}
style={{
border: isActive ? '2px solid blue' : 'none',
maxWidth: '100%'
}}
/>
{children}
</div>
);
case 'video':
return (
<div {...attributes}>
<video
src={element.url}
controls
style={{
border: isActive ? '2px solid blue' : 'none',
maxWidth: '100%'
}}
/>
{children}
</div>
);
default:
return <div {...attributes}>{children}</div>;
}
};Render void elements (elements that don't contain editable text):
import React from 'react';
import { RenderElementProps } from 'slate-react';
const VoidElement = ({ attributes, children, element }: RenderElementProps) => {
switch (element.type) {
case 'image':
return (
<div {...attributes}>
<div contentEditable={false}>
<img src={element.url} alt={element.alt} />
</div>
{children}
</div>
);
case 'horizontal-rule':
return (
<div {...attributes}>
<div contentEditable={false}>
<hr />
</div>
{children}
</div>
);
default:
return <div {...attributes}>{children}</div>;
}
};Create interactive elements with event handlers:
import React from 'react';
import { useSlateStatic, RenderElementProps } from 'slate-react';
import { Transforms } from 'slate';
const InteractiveElement = ({ attributes, children, element }: RenderElementProps) => {
const editor = useSlateStatic();
const handleButtonClick = () => {
const path = ReactEditor.findPath(editor, element);
Transforms.setNodes(
editor,
{ clicked: !element.clicked },
{ at: path }
);
};
if (element.type === 'button') {
return (
<div {...attributes}>
<button
contentEditable={false}
onClick={handleButtonClick}
style={{
backgroundColor: element.clicked ? 'green' : 'gray',
color: 'white',
border: 'none',
padding: '8px 16px',
cursor: 'pointer'
}}
>
{element.text || 'Click me'}
</button>
{children}
</div>
);
}
return <div {...attributes}>{children}</div>;
};Handle complex text formatting combinations:
import React from 'react';
import { RenderLeafProps } from 'slate-react';
const ComplexLeaf = ({ attributes, children, leaf }: RenderLeafProps) => {
let element = children;
// Apply formatting in specific order
if (leaf.code) {
element = <code>{element}</code>;
}
if (leaf.bold) {
element = <strong>{element}</strong>;
}
if (leaf.italic) {
element = <em>{element}</em>;
}
if (leaf.underline) {
element = <u>{element}</u>;
}
if (leaf.strikethrough) {
element = <del>{element}</del>;
}
if (leaf.highlight) {
element = (
<span style={{ backgroundColor: leaf.highlightColor || 'yellow' }}>
{element}
</span>
);
}
if (leaf.fontSize) {
element = (
<span style={{ fontSize: `${leaf.fontSize}px` }}>
{element}
</span>
);
}
if (leaf.color) {
element = (
<span style={{ color: leaf.color }}>
{element}
</span>
);
}
return <span {...attributes}>{element}</span>;
};Always spread the attributes prop on your root element:
// ✅ Correct
const MyElement = ({ attributes, children }: RenderElementProps) => (
<div {...attributes}>{children}</div>
);
// ❌ Incorrect - missing attributes
const BadElement = ({ children }: RenderElementProps) => (
<div>{children}</div>
);Always render the children prop:
// ✅ Correct
const MyElement = ({ attributes, children }: RenderElementProps) => (
<div {...attributes}>
<h1>My Header</h1>
{children}
</div>
);
// ❌ Incorrect - missing children
const BadElement = ({ attributes }: RenderElementProps) => (
<div {...attributes}>
<h1>My Header</h1>
</div>
);For void elements or interactive components:
const ImageElement = ({ attributes, children, element }: RenderElementProps) => (
<div {...attributes}>
<div contentEditable={false}>
<img src={element.url} alt={element.alt} />
</div>
{children}
</div>
);Install with Tessl CLI
npx tessl i tessl/npm-slate-react