---
title: "JavaScript Rich Text Editor"
description: "The LemonadeJS Rich Text Editor block for JavaScript: Rich text on the Toolbar block, with tables, print and Word export. Contract-verified, framework-agnostic, zero dependencies, with a live example."
source: https://lemonadejs.com/docs/plugins/editor/
---

<link rel="stylesheet" href="/v6/toolbar.css">
<link rel="stylesheet" href="/v6/contextmenu.css">
<link rel="stylesheet" href="/v6/modal.css">
<link rel="stylesheet" href="/v6/color.css">
<link rel="stylesheet" href="/v6/editor.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined">

# JavaScript Rich Text Editor

`@lemonadejs/editor` · ✓ 19 contract checks · framework-agnostic · zero dependencies

`<Editor />` — a rich text editor built ON the Toolbar block (the bar is
a dependency, not a fork: the editor drives it through live item
mutations + api.refresh()). Contenteditable engine, semantic HTML out.

  - formatting: block styles (paragraph, headings, quote, code), bold /
    italic / underline / strikethrough, sub / superscript, text and
    highlight colors (the Color block panel, hosted by the Toolbar's
    color items), alignment, ordered / unordered lists, indent /
    outdent, links (inline balloon), inline images (file picker, paste
    and drop — stored as data URLs), horizontal rule, clear
    formatting, fullscreen
  - images are objects: click one (or Tab to it — images are
    focusable, focusing selects) for drag-resize — corner handles
    keep the aspect ratio, edge handles stretch width or height
    independently — and an alignment menu: wrap left / center / wrap
    right / inline, full width, remove; Delete removes the selection
  - tables, CKEditor-style: a hover grid picker inserts (keyboard
    too: arrows size the grid, Enter inserts, Escape closes); a floating
    balloon over the active table carries Row / Column / Cell menus
    (insert above/below/left/right, header row/column, delete), cell
    merge (drag across cells to select, or merge right/down), split,
    cell background color and table delete. Column boundaries, row
    boundaries and the table edge grow DRAG HANDLES for resizing —
    widths land on the colgroup, so they survive as plain HTML.
  - own undo/redo stack (snapshots, typing coalesced) — structural
    table surgery is undoable, which native execCommand undo never was
  - paste is sanitized to a semantic subset (Word/Docs noise dropped,
    script vectors removed) — raw paste with filterpaste="false"
  - output extensions, all local to the browser: DIRECT PDF generation
    (api.exportPDF — the built-in zero-dependency writer lays the
    content out and downloads real selectable-text PDF bytes), print
    through a staged print document (api.print), and Word export as an
    MHTML .doc download (api.exportWord)

Two-way value: bind carries the HTML (onchange fires per input); the
value prop seeds an unbound editor.

## Example

<!--example-->

```js
import { html } from 'lemonadejs';
import Editor from '@lemonadejs/editor';

const NOTE = [
    '<h2>Release notes - September</h2>',
    '<p>The <b>Editor</b> block sits on the Toolbar block: headings, lists, links, ',
    'images and <i>real tables</i> with resizable columns.</p>',
    '<ul><li>Undo and redo with structural table changes</li>',
    '<li>Sanitized paste from Word and Google Docs</li>',
    '<li>Print, PDF and Word export, all in the browser</li></ul>',
].join('');

const App = (props, { state }) => {
    const content = state(NOTE);
    let editor;

    return html`<div>
        <${Editor} bind="${content}" height="260px" placeholder="Write something..."
            ref="${(api) => (editor = api)}" />
        <p style="margin-top:8px">
            <button onclick="${() => editor.insertTable(3, 3)}">Insert table</button>
            <button onclick="${() => editor.undo()}">Undo</button>
            ${() => ' ' + content.value.replace(/<[^>]+>/g, ' ').trim().split(/\s+/).length + ' words'}
        </p>
    </div>`;
};
```

## Installation

```bash
npm install @lemonadejs/editor
```

```js
import Editor from '@lemonadejs/editor';
import '@lemonadejs/editor/style.css';
```

The icons come from Google Material Symbols. Load the font once per page:

```html
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined">
```

Three deployment forms, one component:

```js
html`<${Editor} />`                       // by value (no registration)
setComponents({ Editor });               // then <Editor /> by name anywhere
createWebComponent(Editor);              // <lm-editor> in plain HTML/any framework
```

## Props

Every declared prop arrives as a **live state** — pass a value for a snapshot or a
state for a two-way live wire. Attribute strings are coerced to the declared type.

| Prop | Type | Default | Description |
|---|---|---|---|
| `bind` | string | — | Two-way bound value. `.set()` fires `onchange`; plain assignment is silent. two-way HTML content (the value prop seeds it when unbound) |
| `value` | string | `''` | initial HTML when unbound |
| `placeholder` | string | `''` | hint shown while the editor is empty |
| `toolbar` | boolean | `true` | false hides the formatting bar |
| `height` | string | `''` | CSS height of the writing area ('' grows with content) |
| `readonly` | boolean | `false` | true locks editing (toolbar disabled, content selectable) |
| `filterpaste` | boolean | `true` | sanitize pasted HTML to the semantic subset |
| `acceptimages` | boolean | `true` | paste / drop / pick images as inline data URLs |

## Events

All event names are lowercase (the platform convention — LJS-305 warns otherwise).

- `onfocus` — (e) the writing area gained focus
- `onblur` — (e) the writing area lost focus

## API

```js
import { ref } from 'lemonadejs';
const editor = ref();
html`<${Editor} ref="${editor}" />`;
// editor.current.getData(...)  ·  editor.current.setData(...)  ·  editor.current.getText(...)  ·  editor.current.exec(...)  ·  editor.current.focus(...)  ·  editor.current.insertTable(...)  ·  editor.current.undo(...)  ·  editor.current.redo(...)  ·  editor.current.toggleSource(...)  ·  editor.current.print(...)  ·  editor.current.exportPDF(...)  ·  editor.current.exportWord(...)
```

- `getData()`
- `setData()`
- `getText()`
- `exec()`
- `focus()`
- `insertTable()`
- `undo()`
- `redo()`
- `toggleSource()`
- `print()`
- `exportPDF()`
- `exportWord()`

## Styling

All classes follow the `lm-editor-*` convention; visual variants are `data-*`
attributes on the root. Override freely — there is no styling engine to fight.

## Contract

The machine-readable schema ships with the package:

```js
import contract from '@lemonadejs/editor/contract.json';
```

`verify.json` carries the conformance proof produced by `verify(Editor)`.

Looking for the v5 plugin? See the [archived v5 documentation](/docs/v5/plugins/).