Skip to content

ExpressiveCSS

Browse documentation

Rich text editor

A text field with a formatting toolbar for writing headings, lists and styled text, built on Tiptap.

View as Markdown

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:

  1. Install Tiptap.
  2. Pass its Editor class and your extensions to RichTextEditor.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-height160px
--md-comp-rich-text-editor-shape16px

No matching pages

  • Source color

    The seed every generated ramp derives from. Pick one and browse the docs — the whole theme follows. Error does not: it is a fixed hue.

    Default source: Mist Iris.

    ExpressiveCSS presets: Lagoon, Harbor, Breeze, Cobalt, Iris.

    Bold

    Soft

    Mist

    Glow