# RichText
A rich text editor with a formatting toolbar, markdown shortcuts, links, task lists, a slash menu, find and replace and image upload.
RichText installs separately; it is not part of `@elements/ui`. The import comes with that install, and the template is used as shown.
```ehtml Example
```
## Parts
| Part | What it is |
|---|---|
| `RichText` | The frame: toolbar, editable content, count line, hidden form control and slash menu. |
| `.rich-text-toolbar` | role="toolbar" with one tab stop; arrow keys move between tools. |
| `.rich-text-tool` | A toolbar button: a .toggle with aria-pressed for marks and blocks. |
| `.rich-text-prose` | The contenteditable, role="textbox" aria-multiline. |
| `.rich-text-footer` | The character count, in a line that is always reserved. |
| `.rich-text-slash` | The slash menu: a listbox at the caret, highlighted with aria-activedescendant. |
| `.rich-text-find` | The find and replace panel (Mod+F): find and replace fields, the match count, previous, next, replace, replace all, and match case, whole words and regex toggles. Floats over the top end of the text. |
| `.rich-text-upload` | An image upload in progress, in the text: file name, percent, cancel and a progress bar. The image replaces it when upload resolves. |
| `.find-and-replace-result` | A find match in the text; the current one also has .find-and-replace-result-current. |
## Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
| `value` | `string` (two-way) | `""` | The document, in format. Bind it to state; the editor writes every change back, and writing it from outside replaces the document. Empty is "". |
| `format` | `"html" \| "json" \| "text"` | `"html"` | What value holds: HTML, the document as JSON in a string (editor.getJSON()), or plain text (paragraphs separated by a blank line). |
| `placeholder` | `string` | `""` | Shown in the empty editor, and set as aria-placeholder. |
| `label` | `string` | `""` | Accessible name of the text box (aria-label). Or pass aria-labelledby in attrs. |
| `tools` | `RichTextTool[]` | `DEFAULT_TOOLS` | The toolbar's controls in order: undo, redo, block, bold, italic, underline, strike, code, highlight, link, bulletList, orderedList, taskList, list (the three lists in one menu), blockquote, codeBlock, horizontalRule, align, clear, image (shown with upload), find, and \| for a separator. |
| `toolbar` | `boolean` | `true` | Shows the toolbar. Shortcuts and markdown input work without it. |
| `stickyToolbar` | `boolean` | `false` | The toolbar sticks to the top of the scrolling page while the editor is in view. |
| `headingLevels` | `(1 \| 2 \| 3 \| 4 \| 5 \| 6)[]` | `[1, 2, 3, 4]` | Heading levels the document allows, the text style menu offers and the slash menu lists. Changing it rebuilds the editor around the same document. |
| `slashMenu` | `boolean` | `true` | Typing / opens a menu of blocks at the caret. |
| `slashOptions` | `RichTextSlashOptions` | `{}` | Suggestion options for the menu: the trigger (char, allowSpaces, allowToIncludeChar, allowedPrefixes, startOfLine, minQueryLength, allow, shouldShow, shouldResetDismissed, findSuggestionMatch, pluginKey), its decoration (decorationTag, decorationClass, decorationContent, decorationEmptyClass), the items (items, initialItems, debounce, command, render with mount) and placement (placement, offset, flip, container, floating, dismissOnOutsideClick). |
| `parseOptions` | `ParseOptions` | `{}` | Parse options for HTML content, such as { preserveWhitespace: "full" }. |
| `coreExtensionOptions` | `CoreExtensionOptions` | `{}` | Options for the built-ins: clipboardTextSerializer.blockSeparator joins copied blocks. |
| `rows` | `number` | `6` | Minimum height of the content, in lines. |
| `maxRows` | `number` | `0` | Height where the content stops growing and scrolls. 0 grows without limit. |
| `limit` | `number` | `0` | Maximum characters. Typing and pasting stop at the limit; the count line shows n / limit. A new limit applies at once. |
| `count` | `boolean` | `false` | Shows the character and word count. On by itself when limit is set. |
| `name` | `string` | `""` | Submits value with a form under this name. |
| `required` | `boolean` | `false` | With name: an empty editor blocks the form's submit and takes focus. |
| `disabled` | `boolean` | `false` | Not editable, not focusable, dimmed; the toolbar is off and the form control is not submitted. |
| `readonly` | `boolean` | `false` | Not editable, still selectable and submitted; the toolbar is off. |
| `invalid` | `boolean` | `false` | Danger border and aria-invalid="true". |
| `autofocus` | `boolean \| "start" \| "end" \| "all"` | `false` | Focuses the editor when it mounts, with the caret at the start, the end (true) or everything selected. |
| `dir` | `"" \| "ltr" \| "rtl" \| "auto"` | `""` | Text direction of the content and the toolbar's arrow keys. Changes apply live. |
| `extensions` | `Extension[]` | `[]` | Your own extensions (Extension.create): key bindings, input rules, editor plugins, commands, and nodes and marks for the schema. Their keys run after the built-in formatting keys. |
| `exclude` | `RichTextBuiltin[]` | `[]` | Built-ins to leave out, as turning off a StarterKit extension does: marks (bold, italic, underline, strike, code, highlight, link, superscript, subscript), blocks (heading, blockquote, bulletList, orderedList, taskList, codeBlock, horizontalRule, hardBreak, image) and helpers (undoRedo, dropcursor, gapcursor, trailingNode). Their tools, slash items, shortcuts and markdown rules go, and pasted content drops them. An extension may define its own node or mark under a left-out name. Changing it rebuilds the editor. |
| `inputRules` | `boolean` | `true` | Markdown shortcuts as you type (# heading, - list, \*\*bold\*\*). false types them literally. |
| `pasteRules` | `boolean` | `true` | Markdown and links in pasted text become formatting. |
| `enableContentCheck` | `boolean` | `false` | Checks content against the schema: content with a node or mark the editor does not know fires onContentError and is refused instead of being dropped. |
| `emitContentError` | `boolean` | `false` | Keeps invalid content parsing as usual but still fires onContentError. |
| `highlights` | `RichTextHighlight[]` | `HIGHLIGHTS` | The highlight popover's swatches: { name, color } with any CSS color. The swatch's data-rich-text-swatch is the name in lower case. |
| `autoOpenLink` | `boolean` | `true` | Opens the link popover when the caret enters a link, with focus left in the text; moving out closes it. false keeps it to Mod + K and the link tool. |
| `find` | `boolean` | `true` | Find and replace on Mod + F and the find tool. false leaves Mod + F to the browser. |
| `findOpen` | `boolean` | `false` | Two-way: the find panel is open. Set it to open or close the panel; Mod + F and Escape write it back. |
| `linkOpen` | `boolean` | `false` | Two-way: the link popover is open. Set it to open the popover with its field focused. |
| `upload` | `((file: File, onProgress: (percent: number) => void, signal: AbortSignal) => Promise) \| null` | `null` | Uploads an image and resolves to its URL. Turns on the image tool, Mod + Shift + I, and image drop and paste. Cancel aborts the signal. |
| `imageAccept` | `string` | `"image/*"` | File types the image chooser, drop and paste accept, as in an input's accept. |
| `imageMaxSize` | `number` | `0` | Largest image in bytes. 0 is no limit. |
| `imageLimit` | `number` | `1` | Images per upload. Above 1 the chooser takes several files. |
| `class` | `string` | `""` | Extra classes on the frame. |
| `attrs` | `Record` | `{}` | Attributes for the text box: id, aria-labelledby, aria-describedby, data-testid. |
| `onValueChange` | `(value: string) => void` | `() => {}` | Called when the editor changes the document. Not called for writes to value from outside. |
| `onCreate` | `(editor: Editor) => void` | `() => {}` | Called once the Editor is ready, for commands (editor.chain().focus().toggleBold().run()) and events (editor.on) the attributes do not cover. |
| `onSelectionUpdate` | `(editor: Editor) => void` | `() => {}` | The selection moved; read editor.state.selection or editor.isActive(). |
| `onFocus` | `(e: FocusEvent) => void` | `() => {}` | The text box gained focus. |
| `onBlur` | `(e: FocusEvent) => void` | `() => {}` | The text box lost focus. |
| `onUploadError` | `(error: Error) => void` | `() => {}` | A file was refused (type, size or count) or its upload failed. |
| `onUploadSuccess` | `(url: string) => void` | `() => {}` | An upload finished and its image is in the text. |
| `onContentError` | `(error: Error) => void` | `() => {}` | Content the schema cannot hold, with enableContentCheck or emitContentError. |
| `onFindOpenChange` | `(open: boolean) => void` | `() => {}` | The find panel opened or closed on its own: Mod + F, the find tool, Escape or close. |
| `onLinkOpenChange` | `(open: boolean) => void` | `() => {}` | The link popover opened or closed on its own. |
| `onSetLink` | `(href: string) => void` | `() => {}` | After the link popover applies a link, with the href written. |
| `onHighlightApplied` | `(highlight: RichTextHighlight) => void` | `() => {}` | After the highlight popover applies a swatch, with its name and color. |
## Slots
| Slot | Content |
|---|---|
| `toolbar` | Extra controls after the built-in tools. Give them class="toggle is-sm rich-text-tool" to join the toolbar's look and arrow-key focus. |
## Keyboard
| Keys | Action |
|---|---|
| `Mod + B / I / U` | Bold, italic, underline. Mod is Cmd on a Mac, Ctrl elsewhere. |
| `Mod + Shift + S` | Strikethrough. |
| `Mod + E` | Inline code. |
| `Mod + Shift + H` | Highlight. |
| `Mod + . / Mod + ,` | Superscript, subscript. Each replaces the other. |
| `Mod + K` | Opens the link popover for the selection. |
| `Mod + F` | Opens find and replace; in the panel, selects the find field. |
| `Enter / Shift + Enter` | In the find field: next and previous match. |
| `Arrow Down / Up, Mod + Shift + F / D` | In the find panel: next and previous match. |
| `Enter (replace field)` | Replaces the current match and moves to the next. |
| `Escape (find panel)` | Closes find, clears the marks and returns to the text. |
| `Mod + Shift + I` | Chooses an image to upload (with upload). |
| `Mod + Alt + 0 to 6` | Paragraph, or heading 1 to 6 (levels in headingLevels). |
| `Mod + Shift + 7 / 8 / 9` | Numbered list, bullet list, to-do list. |
| `Mod + Shift + B` | Quote. |
| `Mod + Alt + C` | Code block. |
| `Mod + Shift + L / E / R / J` | Align left, center, right, justify. |
| `Mod + Z / Mod + Shift + Z` | Undo, redo (Mod + Y also redoes). |
| `Shift + Enter` | Line break inside a paragraph. |
| `Tab / Shift + Tab` | In a list: indent and outdent the item. Elsewhere: leaves the editor. |
| `Alt + F10` | From the text: moves focus to the toolbar. |
| `Arrow Left / Right, Home, End` | In the toolbar: move between tools. |
| `Escape` | In the toolbar: back to the text. In the slash menu or a popover: closes it. Inside a Dialog these stay the editor's, so the Dialog stays open. |
| `/` | Opens the slash menu; type to filter, Arrow Up / Down to move, Enter or Tab to insert. |
| `` # ## ### - 1. [] > ``` --- `` | Markdown shortcuts at the start of a line: headings, lists, to-do, quote, code block, divider. |
| `` **text** *text* ~~text~~ `text` ==text== `` | Markdown marks: bold, italic, strike, code, highlight. |
| `" ... -> (c) 1/2` | Typography as you type: smart quotes, ellipsis, arrows, symbols and fractions. |
## Data attributes
| Attribute | Values | On | Description |
|---|---|---|---|
| `data-focused` | present | .rich-text | While the text box has focus. |
| `data-empty` | present | .rich-text | While the document is empty. |
| `data-ready` | present | .rich-text | Once the editor has mounted. |
| `data-disabled` | present | .rich-text | |
| `data-readonly` | present | .rich-text | |
| `data-invalid` | present | .rich-text | |
| `data-state` | `"on" \| "off"` | .rich-text-tool | on while the mark, block or alignment is active at the selection. |
| `data-highlighted` | present | .rich-text-slash-item | |
| `data-loading` | present while items() is pending (aria-busy too) | .rich-text-slash | |
| `data-limit-reached` | present | .rich-text-footer | |
| `data-rich-text-find-option` | `"match-case" \| "whole-words" \| "use-regex"` | .rich-text-find button | Which match option a toggle sets; data-state on or off. |
## Accessibility
- The content is a contenteditable with role="textbox" and aria-multiline="true", named by label or aria-labelledby, with aria-placeholder, aria-readonly, aria-disabled, aria-invalid and aria-required as they apply.
- The toolbar is role="toolbar" with aria-controls pointing at the text box and one tab stop; arrow keys move between tools (WAI-ARIA toolbar pattern). Alt + F10 reaches it from the text and Escape returns.
- Mark, block and alignment tools are toggle buttons with aria-pressed and aria-keyshortcuts; a tool that cannot apply at the selection is aria-disabled but stays focusable.
- Pressing a tool with the mouse does not move focus, so the selection stays in the text.
- The slash menu is a listbox; the text box gets aria-expanded, aria-controls and aria-activedescendant so a screen reader follows the highlight while typing continues.
- The count line is aria-live="polite" and is referenced by aria-describedby when limit is set.
- The find panel is role="dialog" named "Search and replace"; its match count is aria-live="polite" and its option toggles carry aria-pressed. Read-only text stays focusable so it can be searched.
- The link popover that opens when the caret enters a link leaves focus in the text, so typing and screen reader reading go on.
- An upload placeholder is a group named for the file with a progressbar and a named cancel button.
## See also
- [CodeEditor](code-editor): A code editor with highlighting, line numbers, folding, completion, search, multiple cursors and diagnostics.