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
| Prop | Type | Default | |
|---|---|---|---|
features | ContentReaderRegistry | readonly ContentFeatureReader[] | required | |
value | ContentValue | null | required | |
options | ContentHtmlOptions | {} | |
exit | Exit | — | Inside a <Presence>: plays this child's way out before it unmounts. |
render | ComponentRenderFn<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.