Server-side rendering

Render stored content without loading the editor.

The editor never produces HTML; it produces a document. Rendering it for a reader is the reader half of every feature, exported on its own from @hoplallc/ui/content-editor/reader, an entry that imports neither Plate nor Slate. A static site build that only renders stored content pays for the renderer alone.

As HTML

<h2>Writing inside your own app</h2><p>Rich text with <strong>bold</strong>, <em>italic</em> and a <a href="https://ui.hopla.llc" rel="noopener noreferrer">link</a>. Type / for a block, or select text for the floating toolbar.</p><aside class="callout"><span aria-hidden="true">💡</span><div>Every block on this page is one feature definition.</div></aside><ul><li>Lists, with Tab to nest</li><li>Tables, images, embeds</li></ul><blockquote>And a quote, for when someone else said it better.</blockquote>
import { contentToHtml, createContentReaders } from "@hoplallc/ui/content-editor/reader";
 
const READERS = createContentReaders();
const html = contentToHtml(post.body, {
	features: READERS,
	imageDimensions,                 // sizes by url, for width and height on <img>
	classNameFor: (type) => …,       // a class per node type, when your stylesheet names them
	siteOrigin: "https://example.com", // links elsewhere get rel="noopener noreferrer"
});

contentToHtml writes bare elements: no class unless classNameFor names one. A link to another origin, or one opening in a new tab, gets rel="noopener noreferrer"; a relative link or one to siteOrigin keeps its referrer. Without siteOrigin, every absolute http(s) link counts as external.

A run of list items becomes one <ul> or <ol> wherever it sits, in a quote or a table cell as at the top level. A table whose first row is header cells gets a <thead>, and a cell holding one paragraph writes its text without the <p>. A code block is one <pre><code>, its language as a language-* class.

contentToInlineHtml unwraps top-level paragraphs and joins them with <br>, for a value rendered inside a block the host already has. collectContentUrls lists every image url, for a build that measures them first.

As React

Writing inside your own app

Rich text with bold, italic and a link. Type / for a block, or select text for the floating toolbar.

  • Lists, with Tab to nest
  • Tables, images, embeds
And a quote, for when someone else said it better.
<ContentRenderer value={post.body} features={READERS} />

The renderer carries the kit's own classes, for a page with no typography of its own. Inside Tailwind's prose, pass unstyled so the elements come out bare and prose styles them; classNameFor still applies.

<ContentRenderer
	value={post.body}
	features={READERS}
	options={{ unstyled: true, siteOrigin: "https://example.com" }}
	render={<article className="prose" />}
/>

Pass createContentReaders the same options as createContentFeatures (headings, embeds) so the reader renders exactly what the editor edits; the registry test holds the two sets to the same node types.

API

PropTypeDefault
featuresContentReaderRegistry | readonly ContentFeatureReader[]required
valueContentValue | nullrequired
optionsContentHtmlOptions{}
exitExit—Inside a <Presence>: plays this child's way out before it unmounts.
renderComponentRenderFn<HTMLProps, {}> | ReactElement<unknown, string | JSXElementConstructor<any>>—Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a ReactElement or a function that returns the element to render.

Plus every <div> prop. Source: content-editor/components/content-renderer.tsx.