Plate
PlateEditorsTemplates
GitHub16kGitHub
DiscordDiscord
    • Overview
    • Why This Fork
    • Examples
    • Installing Plite
    • Adding Event Handlers
    • Defining Custom Elements
    • Applying Custom Formatting
    • Executing Commands
    • Saving to a Database
    • Canonical Change Substrate
    • Improving Performance
    • Interfaces
    • Nodes
    • Locations
    • Transforms
    • Document Changes
    • Commands
    • Editor
    • Plugins
    • Rendering
    • Serializing
    • Normalizing
    • Using TypeScript
    • Roots
    • Document State
    • Editing Behavior
    • Selection And DOM
    • Clipboard And Paste
    • Decorations, annotations, and widgets
    • Schema
    • Anchor API
    • Location API
    • Path API
    • PointEntry API
    • Point API
    • Range API
    • Selection API
    • Location Types APIs
    • Span API
    • Editor
    • Element API
    • NodeEntry API
    • Node API
    • Node Types APIs
    • Text API
    • Debug Value Scrubbing
    • Transforms API
    • Plite DOM
    • History Editor API
    • History Plugin Setup
    • History
    • Plite History
    • Plite Hyperscript
    • Plite PaginationExperimental
    • Annotations
    • DOM Coverage Boundaries
    • Editable Component
    • External Text Views
    • Plite React Event Handling
    • Virtualized Rendering
    • Plite React Hooks
    • React Editor Setup
    • React Editor
    • Plite React
    • Plite Component
    • Plite Yjs
    • Plite
    • Migration
    • Contributing
    • FAQ
    • Resources

Rendering

PreviousNext

Render elements, leaves, text, overlays, voids, and hidden content with Plite React.

Plite React renders your document with normal React components. You provide the components for product-specific elements, text nodes, leaves, toolbars, menus, and overlays. Pass stable render props to Editable so React does not receive a fresh renderer on every render.

Elements

import { EditorRoot, Editable, useEditor } from "plitejs/react";
 
const ParagraphElement = ({ attributes, children }) => {
  return <p {...attributes}>{children}</p>;
};
 
const QuoteElement =






























PluginsSerializing

On This Page

ElementsLeavesTextsDecorationsToolbars, menus, overlays, and more!Editor stylingPerformance
Build your editor
Production-ready AI template and reusable components.
Get all-access
({
attributes
,
children
})
=>
{
return <blockquote {...attributes}>{children}</blockquote>;
};
const LinkElement = ({ attributes, children, element }) => {
return (
<a {...attributes} href={element.url}>
{children}
</a>
);
};
const renderElement = (props) => {
switch (props.element.type) {
case "quote":
return <QuoteElement {...props} />;
case "link":
return <LinkElement {...props} />;
default:
return <ParagraphElement {...props} />;
}
};
const MyEditor = () => {
const editor = useEditor({ initialValue });
return (
<EditorRoot editor={editor}>
<Editable renderElement={renderElement} />
</EditorRoot>
);
};
import { EditorRoot, Editable, useEditor } from "plitejs/react";
 
const ParagraphElement = ({ attributes, children }) => {
  return <p {...attributes}>{children}</p>;
};
 
const QuoteElement = ({ attributes, children }) => {
  return <blockquote {...attributes}>{children}</blockquote>;
};
 
const LinkElement = ({ attributes, children, element }) => {
  return (
    <a {...attributes} href={element.url}>
      {children}
    </a>
  );
};
 
const renderElement = (props) => {
  switch (props.element.type) {
    case "quote":
      return <QuoteElement {...props} />;
    case "link":
      return <LinkElement {...props} />;
    default:
      return <ParagraphElement {...props} />;
  }
};
 
const MyEditor = () => {
  const editor = useEditor({ initialValue });
 
  return (
    <EditorRoot editor={editor}>
      <Editable renderElement={renderElement} />
    </EditorRoot>
  );
};

Always spread props.attributes on the top-level DOM element returned by the custom renderer, and always render props.children. The attributes connect the DOM node to Plite's DOM helpers. The children render text leaves, inline elements, void anchors, and nested editable content.

Leaves

When text-level formatting is rendered, characters are grouped into leaves that share the same persisted marks.

To customize leaf rendering, pass renderLeaf:

const renderLeaf = ({ attributes, children, leaf }) => {
  if (leaf.bold) {
    children = <strong>{children}</strong>;
  }
  if (leaf.italic) {
    children = <em>{children}</em>;
  }
 
  return <span {...attributes}>{children}</span>;
};
const renderLeaf = ({ attributes, children, leaf }) => {
  if (leaf.bold) {
    children = <strong>{children}</strong>;
  }
  if (leaf.italic) {
    children = <em>{children}</em>;
  }
 
  return <span {...attributes}>{children}</span>;
};

Keep spreading props.attributes and rendering props.children.

When decorations split a text node into rendered segments, renderLeaf receives leafPosition. It contains the segment's start and end offsets plus optional isFirst and isLast flags. Decoration attributes are already applied inside children; they never become fields on leaf.

One disadvantage of text-level formatting is that you cannot guarantee that any given format is "contiguous"—meaning that it stays as a single leaf. This limitation with respect to leaves is similar to the DOM, where this is invalid:

<em>t<strong>e</em>x</strong>t
<em>t<strong>e</em>x</strong>t

Because the elements in the above example do not properly close themselves they are invalid. Instead, you would write the above HTML as follows:

<em>t</em><strong><em>e</em>x</strong>t
<em>t</em><strong><em>e</em>x</strong>t

If you happened to add another overlapping section of <strike> to that text, you might have to rearrange the closing tags yet again. Rendering leaves in Plite is similar—you can't guarantee that even though a word has one type of formatting applied to it that that leaf will be contiguous, because it depends on how it overlaps with other formatting.

Use text-level formatting and element-level formatting for different jobs:

  • Text properties are for non-contiguous, character-level formatting.
  • Element properties are for contiguous, semantic elements in the document.

Texts

renderLeaf customizes persisted mark presentation. Use renderText when you need one wrapper around the entire text node, regardless of Decoration boundaries.

This is where renderText comes in. It lets you render a component that wraps all the leaves generated for a single Text node.

const TextNode = ({ attributes, children, text }) => {
  return (
    <span {...attributes} className="custom-text" data-length={text.text.length}>
      {children}
    </span>
  );
};
 
<Editable renderText={TextNode} />;
const TextNode = ({ attributes, children, text }) => {
  return (
    <span {...attributes} className="custom-text" data-length={text.text.length}>
      {children}
    </span>
  );
};
 
<Editable renderText={TextNode} />;

When to use renderLeaf vs renderText:

  • renderLeaf: Use this for persisted marks such as leaf.bold. It may run more than once for one text node when Decoration boundaries split the rendered content. Use leafPosition only when mark presentation depends on segment position.

  • renderText: Use this when you need to render something exactly once for a given text node, regardless of how many leaves it's split into. It's ideal for wrapping the entire text node's content or adding elements associated with the text node as a whole without worrying about duplication caused by decorations.

You can use renderText and renderLeaf together. renderLeaf renders marks, Decoration spans wrap transient ranges inside its children, and renderText renders the container around every segment.

Decorations

Decorations are transient range attributes. Configure ordered sources on <EditorRoot decorations={sources}>; each source returns keyed ranges with className, style, aria-*, or data-* attributes. Editable applies those attributes automatically. Decorations never become text properties or leaf renderer inputs.

Use Decorations for search results, syntax highlighting, and external diagnostics. Use marks for persisted text formatting. See Decorations and annotations for source observation and refresh.

Toolbars, menus, overlays, and more!

In addition to controlling the rendering of nodes inside Plite, descendants can retrieve the provider editor with useEditorContext().

Use editor-level hooks for shell UI such as toolbars. Keep broad editor subscriptions out of rendered document nodes.

A common use case for this is rendering a toolbar with formatting buttons that are highlighted based on the current selection:

const MyEditor = () => {
  const editor = useEditor({ initialValue });
 
  return (
    <EditorRoot editor={editor}>
      <Toolbar />
      <Editable />
    </EditorRoot>
  );
};
 
import { useEditorState } from "plitejs/react";
 
const Toolbar = () => {
  const isBold = useEditorState((state) => {
    return state.marks()?.bold === true;
  });
 
  return (
    <div>
      <button type="button" aria-pressed={isBold}>B</button>
      <button type="button">I</button>
    </div>
  );
};
const MyEditor = () => {
  const editor = useEditor({ initialValue });
 
  return (
    <EditorRoot editor={editor}>
      <Toolbar />
      <Editable />
    </EditorRoot>
  );
};
 
import { useEditorState } from "plitejs/react";
 
const Toolbar = () => {
  const isBold = useEditorState((state) => {
    return state.marks()?.bold === true








Because the toolbar draws editor-level state, useEditorState is the right level of subscription. It runs the selector inside editor.read and re-renders only when the selected result changes. Element renderers should prefer target-scoped hooks such as useElementSelected(), useElementSelected({ mode: 'collapsed' }), useNodeSelector, and useTextSelector.

Editor styling

Custom styles can be applied to the editor itself by using the style prop on the <Editable> component.

const MyEditor = () => {
  const editor = useEditor({ initialValue });
 
  return (
    <EditorRoot editor={editor}>
      <Editable style={{ minHeight: "200px", backgroundColor: "lime" }} />
    </EditorRoot>
  );
};
const MyEditor = () => {
  const editor = useEditor({ initialValue });
 
  return (
    <EditorRoot editor={editor}>
      <Editable style={{ minHeight: "200px", backgroundColor: "lime" }} />
    </EditorRoot>
  );
};

It is also possible to apply custom styles with a stylesheet and className. However, Plite uses inline styles to provide some default styles for the editor. Because inline styles take precedence over stylesheets, styles you provide using stylesheets will not override the default styles. If you are trying to use a stylesheet and your rules are not taking effect, do one of the following:

  • Provide your styles using the style prop instead of a stylesheet, which overrides the default inline styles.
  • Pass the disableDefaultStyles prop to the <Editable> component.
  • Use !important in your stylesheet declarations to make them override the inline styles.

Performance

See Improving Performance for ways to improve the rendering performance of the editor.

;
});
return (
<div>
<button type="button" aria-pressed={isBold}>B</button>
<button type="button">I</button>
</div>
);
};