Rich Text Editor
A controlled WYSIWYG editor. value is an HTML string — you own
it in state and apply the next one in onChange
(v-model in Vue). Built on native
contentEditable with zero dependencies; output
is semantic HTML with no inline styles, so it's easy to sanitise and store.
Basic
The full toolbar: bold, italic, underline, strikethrough, headings,
ordered/unordered lists, quote, code block, link, and clear formatting.
Every keystroke calls onChange with the latest HTML.
174 chars of HTML
import { useState } from "react";
import { RichTextEditor } from "@usevyre/react";
const [html, setHtml] = useState(
"<h2>Release notes</h2><p>useVyre <strong>v1.2</strong> ships a zero-dep editor.</p>"
);
<RichTextEditor
value={html}
onChange={setHtml}
placeholder="Write release notes…"
/> <script setup>
import { ref } from "vue";
import { RichTextEditor } from "@usevyre/vue";
const html = ref(
"<h2>Release notes</h2><p>useVyre <strong>v1.2</strong> ships a zero-dep editor.</p>"
);
</script>
<template>
<RichTextEditor v-model="html" placeholder="Write release notes…" />
</template> Custom toolbar
Pass toolbar to limit the available actions to just what you
need, and minHeight to size the editing area.
<RichTextEditor
value={html}
onChange={setHtml}
toolbar={["bold", "italic", "link", "clear"]}
minHeight="6rem"
/> <RichTextEditor
v-model="html"
:toolbar="['bold', 'italic', 'link', 'clear']"
min-height="6rem"
/> Props
Props
| Prop | Type | Default | Description |
|---|---|---|---|
value / v-model | string | — | HTML content (controlled). React: value + onChange; Vue: v-model. |
onChange / @update:modelValue | (html: string) => void | — | Called with the next HTML string on every edit. |
placeholder | string | "Write something…" | Shown when the editor is empty. |
disabled | boolean | false | Not editable, dimmed; toolbar buttons disabled. |
readOnly | boolean | false | Not editable; toolbar hidden entirely (render-only). |
toolbar | RichTextTool[] | all | Which buttons to show: "bold" | "italic" | "underline" | "strike" | "h1" | "h2" | "h3" | "ul" | "ol" | "quote" | "code" | "link" | "clear". |
minHeight | string | "10rem" | CSS min-height of the editable area. |
sanitize | (html: string) => string | — | Optional sanitizer run on render-in and emit-out. The editor is zero-dependency and does NOT sanitize untrusted HTML by itself — pass your own (e.g. DOMPurify.sanitize) for untrusted content. The link tool always blocks javascript:/data:/vbscript: URLs. |
className / class | string | — | Additional CSS class on the root element. |
Security note: value is raw user-authored HTML.
Always sanitise it (e.g. with DOMPurify) before re-rendering it elsewhere
with dangerouslySetInnerHTML / v-html.
Valid props
| Prop | Values | Default |
|---|---|---|
disabled | true|false | false |
readOnly | true|false | false |
Common AI mistakes
- RichTextEditor without value/onChange (React) or v-model (Vue)→ Keep the HTML string in state and update it in onChange / v-model
- Binding untrusted HTML to value without the sanitize prop→ Pass sanitize={DOMPurify.sanitize} (or sanitize before storing) for untrusted content
- Rendering value as text or with dangerouslySetInnerHTML elsewhere without sanitising→ Sanitise (e.g. DOMPurify) before re-rendering untrusted RTE output
- toolbar="bold" (string)→ Pass an array, e.g. toolbar={["bold","italic","link"]}
Quick examples
const [html, setHtml] = useState("<p>Hello <strong>world</strong></p>");
<RichTextEditor value={html} onChange={setHtml} placeholder="Write…" /><RichTextEditor
value={html}
onChange={setHtml}
toolbar={["bold", "italic", "link"]}
/>