/// Inputs
RichTextEditor
A bordered, sunken WYSIWYG surface with a mono formatting toolbar, built on tiptap. Controlled via value/onChange.
import { RichTextEditor } from "@protocore/pds";Optional peer dependencies
RichTextEditor ships on the `@protocore/pds/editor` subpath so its editor engine stays out of the main bundle — exactly like charts keep recharts out. It requires the optional tiptap peers, installed alongside @protocore/pds:
npm i @tiptap/react @tiptap/pm @tiptap/starter-kit @tiptap/extension-link @tiptap/extension-placeholder
Import it from the subpath: import { RichTextEditor } from "@protocore/pds/editor";
Controlled editor
Pass value + onChange to control the document. The toolbar toggles bold, *italic*, strike, inline code, a heading, bullet / numbered lists, blockquote, and links; the active mark inverts to the solid fill. All the usual keyboard shortcuts (⌘B, ⌘I, …) come from tiptap.
Comment composer
A compact editor with a lower minHeight, wired to a submit row. Strip the tags to detect an empty document before enabling the action.
Read only
Set readOnly to present stored rich text without an editing affordance — the toolbar disables and the surface is not editable, but content still renders with the document typography.
When to use it
Reach for RichTextEditor when authors need *formatted* prose — headings, lists, links, emphasis — such as CMS body content, changelog entries, or support replies. For plain, unformatted multi-line text (a note, a description, an address) use Textarea: it is lighter, has no peer dependencies, and posts a simple string. For read-only display of code, use CodeBlock.
Usage
Do
- Install the optional `@tiptap/*` peers, and import from `@protocore/pds/editor`.
- Give the editor an accessible name via `aria-label` (or an associated `Field` label).
- Keep the document controlled with `value` / `onChange`, or uncontrolled with `defaultValue` — not a mix.
- Persist and re-hydrate with a single serialization — HTML *or* JSON via `format` — consistently.
Don't
- Don't import RichTextEditor from the main `@protocore/pds` entry — it lives on the `/editor` subpath.
- Don't use it for plain text — a Textarea is the right, dependency-free control.
- Don't feed `value` a string in a different `format` than you configured — the editor can't parse it.
- Don't rely on it rendering without the peers installed; the subpath is intentionally dependency-gated.
Accessibility
| Keys | Action |
|---|---|
| ⌘/Ctrl + B | Toggle bold on the selection. |
| ⌘/Ctrl + I | Toggle italic on the selection. |
| Tab | Move focus from the surface out of the editor (does not insert a tab). |
| Enter | New paragraph; inside a list, a new list item. |
| Backspace at line start | Lift a list item / blockquote back to a paragraph. |
- The writing surface exposes `role="textbox"` with `aria-multiline="true"` and the `aria-label` you provide.
- Every toolbar button is icon-only with an `aria-label`, sits in a `role="toolbar"` group, and carries `aria-pressed` reflecting whether its mark/node is active at the cursor.
- In `readOnly` mode the toolbar buttons are `disabled` and the surface is not editable.
- Toolbar buttons preventDefault on mousedown so clicking one keeps the text selection, applying the command to the intended range.
RichTextEditor props
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | Controlled document value. HTML by default; a stringified ProseMirror JSON when `format="json"`. Pair with `onChange`. |
defaultValue | string | — | Uncontrolled initial value, in the same serialization as `value`. |
onChange | (value: string) => void | — | Fired with the serialized document on every edit (HTML, or JSON when `format="json"`). |
format | "html" | "json" | "html" | Serialization used for `value` / `onChange`. |
placeholder | string | — | Muted placeholder shown while the document is empty. |
readOnly | boolean | false | Render read-only — the toolbar disables and the surface is not editable. |
toolbar | boolean | true | Show the formatting toolbar. |
minHeight | number | 180 | Minimum height of the writing surface, in px. |
aria-label | string | "Rich text editor" | Accessible name for the editable region. |
editorRef | (editor: Editor | null) => void | — | Escape hatch — receives the underlying tiptap `Editor` instance once ready (or `null`). |