A .rich-text-editor turns a <textarea>
into an editor with a formatting toolbar. It is built on
Tiptap, which the page supplies.
Put a docked toolbar and a
<textarea> in the frame. The textarea's value is
the starting HTML. The component hides the textarea, mounts the
editor in a .rich-text-editor-content after it, and
writes the editor's HTML back on every change. Without JavaScript,
the textarea stays visible and the toolbar is hidden.
<label for="body">Release notes</label>
<div class="rich-text-editor">
<div class="toolbar docked">
<button type="button" class="circle" data-command="toggleBold" aria-label="Bold">
<span class="material-symbols" aria-hidden="true">format_bold</span>
</button>
<button type="button" class="circle" data-command="toggleHeading"
data-attributes='{"level":2}' aria-label="Heading">
<span class="material-symbols" aria-hidden="true">format_h2</span>
</button>
<span class="max"></span>
<button type="button" class="circle" data-command="undo" aria-label="Undo">
<span class="material-symbols" aria-hidden="true">undo</span>
</button>
</div>
<textarea id="body" name="body"><p>Starting content</p></textarea>
</div>
The editable area is a multi-line textbox. It takes its
name from the textarea's <label>,
aria-label or aria-labelledby. Clicking
the label focuses it. Tiptap's keyboard shortcuts work, such as
Ctrl+B for bold and Ctrl+Z
to undo. Tab moves to the toolbar buttons.
Setup
ExpressiveCSS does not bundle Tiptap, and AutoInit()
does not start the editor. To set it up:
- Install Tiptap.
-
Pass its
Editorclass and your extensions toRichTextEditor.init().
npm install @tiptap/core @tiptap/pm @tiptap/starter-kit
import { Editor } from '@tiptap/core';
import StarterKit from '@tiptap/starter-kit';
import { RichTextEditor } from '@expressivecss/expressive';
RichTextEditor.init(document.querySelector('.rich-text-editor'), {
Editor,
extensions: [StarterKit]
});
Commands
A button's data-command names a Tiptap command, and
data-attributes holds its argument as JSON. A click runs
the command on the selection and returns focus to the editor. Any
command your extensions add works the same way.
A button is disabled while its command cannot run, for example Undo
with nothing to undo. Buttons for toggle commands also
show whether the selection is formatted. toggleBold sets
aria-pressed and .active while the
selection is bold. toggleHeading with
{"level":2} sets them while the selection is in a
second-level heading.
Forms
The textarea submits the editor's HTML with its form and fires
input on every change. An empty editor submits an empty
value. Tiptap drops markup its extensions do not know. The value
still comes from the browser, so sanitize it on the server before you
store or show it. A disabled or readonly
textarea makes the editor read-only and disables the toolbar.
Options
| Name | Type | Default | Description |
|---|---|---|---|
Editor |
Class | null |
Tiptap's Editor class. Required. |
extensions |
Array | [] |
Tiptap extensions, usually [StarterKit]. |
Methods
const instance = Expressive.RichTextEditor.getInstance(document.querySelector('.rich-text-editor'));
instance.editor.commands.setContent('<p>Replaced</p>'); // the Tiptap editor
instance.destroy(); // removes the editor and shows the textarea again
Tokens
Override these on the .rich-text-editor.
| Token | Default |
|---|---|
--md-comp-rich-text-editor-min-height | 160px |
--md-comp-rich-text-editor-shape | 16px |