A TipTap rich text editor with a toolbar (bold, italic, headings 2 and 3, bulleted and numbered lists, quote, link, image), plus `RichText`, the renderer for the HTML it stores, with print-friendly prose styles.

pnpm add @behivetech/molecules.rich-text-editor

This is the snapshot of v0.1.5, the current version, as recorded on 2026-11-10. The live page runs its examples →

Examples of v0.1.5 are shown as code; only the latest version runs live. Open the live examples →

A controlled editor in a `FormField`. The preview below it is `RichText` on the same HTML.

Basic Rich Text Editor
1const SAMPLE_HTML =
2 '<h2>Before you get in</h2><p>The hot tub is on the back deck, past the <strong>string lights</strong>. It stays at 102°F all year.</p><ul><li>Shower first.</li><li>Keep the cover on whenever nobody is in.</li></ul><h3>Turning on the jets</h3><ol><li>Lift the cover from the house side.</li><li>Press <strong>Jets</strong> once for low, twice for high.</li></ol><blockquote><p>Please be out of the tub by 10 pm.</p></blockquote><p>Something not working? See <a href="/guide/who-to-call">Who to call</a>. <em>Never drain the tub yourself.</em></p>';
3
4const BasicRichTextEditor = () => {
5 const [html, setHtml] = useState(SAMPLE_HTML);
6
7 return (
8 <>
9 <FormField
10 label="About the house"
11 htmlFor="about"
12 hint="Shown on the home page."
13 >
14 <RichTextEditor
15 id="about"
16 value={html}
17 onChange={setHtml}
18 aria-label="About the house"
19 />
20 </FormField>
21 <RichText html={html} />
22 </>
23 );
24};
25
26render(<BasicRichTextEditor />);

With `onInsertImage`, the toolbar shows an Image button. The picker here resolves with a stand-in image.

With Image Rich Text Editor
1const WithImageRichTextEditor = () => {
2 const [html, setHtml] = useState("<p>Add a photo with the image button.</p>");
3
4 return (
5 <RichTextEditor
6 value={html}
7 onChange={setHtml}
8 aria-label="Page body"
9 onInsertImage={async () => ({
10 src: "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='640' height='360'><rect width='640' height='360' fill='%23bc13fe'/></svg>",
11 alt: "A purple placeholder",
12 width: 640,
13 height: 360,
14 })}
15 />
16 );
17};
18
19render(<WithImageRichTextEditor />);

`features` limits the toolbar to the formats a field needs, here a short bio.

Bio Rich Text Editor
1const BioRichTextEditor = () => {
2 const [html, setHtml] = useState("<p>Painter and potter.</p>");
3
4 return (
5 <RichTextEditor
6 value={html}
7 onChange={setHtml}
8 aria-label="Artist bio"
9 features={["bold", "italic", "link"]}
10 />
11 );
12};
13
14render(<BioRichTextEditor />);

Invalid state for a required field that is empty; `FormField` shows the message.

Invalid Rich Text Editor
1const InvalidRichTextEditor = () => (
2 <FormField label="Body" htmlFor="body" error="Add some text." required>
3 <RichTextEditor
4 id="body"
5 value=""
6 onChange={() => {}}
7 aria-label="Body"
8 invalid
9 />
10 </FormField>
11);
12
13render(<InvalidRichTextEditor />);