Other
Editor
Minimal dependency-free rich text editor built on contentEditable, with a formatting toolbar (bold, italic, headings, lists, blockquote, code, links, alignment, text/highlight colors, images), a configurable command set, sanitized HTML pasting via stripUnsafeHtml and controlled or uncontrolled HTML value. Security note: value/defaultValue is rendered as-is by default — enable the opt-in sanitizeValue prop (or call stripUnsafeHtml yourself) whenever the HTML comes from an untrusted source.
Import
import { Editor, stripUnsafeHtml } from "@zephora/react";Examples
Uncontrolled
Hello Zephora!
<Editor defaultValue="<p>Hello <b>Zephora</b>!</p>" placeholder="Write something…" />
Controlled
`onValueChange` receives the current HTML after every edit.
Type here…
<p>Type here…</p>
const [html, setHtml] = React.useState("<p>Type here…</p>");
<Editor value={html} onValueChange={setHtml} minHeight={120} />
<pre>{html}</pre>Read only
`readOnly` disables editing and the toolbar.
Release notes
This content cannot be edited.
<Editor defaultValue="<h2>Release notes</h2><p>This content cannot be edited.</p>" readOnly />
Full toolbar
The default toolbar includes links (select text first), alignment, text/highlight colors and image insertion. `commands` picks which buttons to show, in the given order.
Select me and try the link and color buttons.
// All commands (the default). Pass a subset to trim the toolbar:
<Editor
commands={[
"bold", "italic", "underline",
"link", "unlink",
"alignLeft", "alignCenter", "alignRight",
"foreColor", "hiliteColor",
"image", "clear",
]}
defaultValue="<p style='text-align: center'><span style='color: #7c3aed'>Select me</span> and try the link and color buttons.</p>"
/>Sanitizing pasted HTML
The editor already runs pasted HTML through stripUnsafeHtml (sanitizeOnPaste, on by default). The util is also exported on its own — here it cleans hostile markup: scripts, event handlers and javascript: URLs are removed, safe tags survive.
— press Sanitize —
import { stripUnsafeHtml } from "@zephora/react";
const [dirty, setDirty] = React.useState(DIRTY_HTML);
const [clean, setClean] = React.useState("");
<textarea value={dirty} onChange={(e) => setDirty(e.target.value)} rows={6} />
<Button onClick={() => setClean(stripUnsafeHtml(dirty))}>Sanitize</Button>
<pre>{clean}</pre>API
Editor props
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | Controlled HTML value. |
defaultValue | string | — | Initial HTML for uncontrolled usage. |
onValueChange | (html: string) => void | — | Called with the current HTML after every edit. |
placeholder | string | — | Hint shown while the editor is empty. |
readOnly | boolean | false | Disables editing and the toolbar. |
minHeight | number | 160 | Minimum block size of the editing area in px. |
commands | EditorCommandName[] | all commands | Which toolbar commands to show, in the given order. EditorCommandName is one of: "bold" | "italic" | "underline" | "strikethrough" | "h1" | "h2" | "ul" | "ol" | "blockquote" | "code" | "link" | "unlink" | "alignLeft" | "alignCenter" | "alignRight" | "foreColor" | "hiliteColor" | "image" | "clear". |
sanitizeOnPaste | boolean | true | Runs pasted HTML through stripUnsafeHtml before insertion (plain-text pastes are left untouched). |
aria-label | string | "Rich text editor" | Accessible name of the editing area. |
unstyled | boolean | false | Headless mode — skips Zephora styling. |
stripUnsafeHtml(html) props
| Prop | Type | Default | Description |
|---|---|---|---|
html * | string | — | HTML string to sanitize. Kept: p, br, b, strong, i, em, u, s, strike, h1–h3, ul, ol, li, blockquote, pre, code, a[href: http/https/mailto], img[src: http/https/data:image + alt], span[style: color/background-color/text-align], div[style: text-align]. script/style/iframe & co. are dropped with their content; unknown-but-harmless containers are unwrapped. |
→ returns | string | — | Sanitized HTML. Event-handler attributes and javascript: URIs are always removed. Pure function — safe to import without rendering the Editor; on the server (no DOM) it falls back to a best-effort regex pass, so run it on the client before trusting the output. |
Keyboard
| Key | Action |
|---|---|
ArrowRight / ArrowLeft | Moves focus between toolbar buttons (wraps around). |
Enter / Space | Applies the focused toolbar command. |
Tab | Moves between the toolbar and the editing area. |
Enter (in a link/image panel) | Applies the URL / inserts the image. |
Escape (in a panel or palette) | Closes it and returns focus to its trigger button. |