---
outline: deep
---

# Prose Editor

A rich text editor built on [Tiptap](https://tiptap.dev) (ProseMirror). Form-associated — its value is the editor HTML, so it submits inside a `<form>` like a native field.

```html
<l-prose-editor placeholder="Write something…"></l-prose-editor>
```

The editable area renders in light DOM, so its content styles ship as a separate stylesheet you import once globally. See [Importing](#importing).

**`<l-prose-editor>`** — Custom Element · Shadow DOM

## Options

### Basic

The default toolbar covers headings, marks, lists, links, code, an emoji picker and undo/redo. Set initial content with `initial-html`.

```html
<l-prose-editor
  placeholder="Write something…"
  initial-html="<h2>Release notes</h2><p>This editor ships with a <strong>default</strong> toolbar covering headings, marks, lists, links and more.</p><ul><li>Built on Tiptap</li><li>Form-associated</li></ul>"
></l-prose-editor>
```

### Toolbar preset

Set `toolbar-preset="minimal"` for a compact bold/italic/underline toolbar.

```html
<l-prose-editor
  toolbar-preset="minimal"
  placeholder="Bold, italic, underline only…"
></l-prose-editor>
```

### Custom toolbar

Set `toolbar` to a comma-separated list of commands to build your own layout. Use `divider` to insert a separator.

```html
<l-prose-editor
  toolbar="bold,italic,link,divider,bulletlist,orderedlist,divider,undo,redo"
  placeholder="Custom toolbar…"
></l-prose-editor>
```

Available commands: `heading-1`, `heading-2`, `heading-3`, `bold`, `italic`, `underline`, `strike`, `highlight`, `bulletlist`, `orderedlist`, `blockquote`, `code-block`, `horizontal-rule`, `link`, `emoji`, `attachment`, `undo`, `redo`, `divider`.

### Toolbar placement

Set `toolbar-placement="bottom"` to move the toolbar below the content.

```html
<l-prose-editor
  toolbar-placement="bottom"
  placeholder="Toolbar sits below the content…"
></l-prose-editor>
```

### Emoji picker data

The `emoji` toolbar button opens a picker that loads its emoji data from a CDN by default. For an offline or behind-auth app, serve an [`emojibase-data`](https://www.npmjs.com/package/emojibase-data) JSON locally and point `emoji-data-source` at it.

```html
<l-prose-editor emoji-data-source="/emoji/en/data.json"></l-prose-editor>
```

## Examples

### Form integration

The editor participates in forms via its `name` attribute. The submitted value is the HTML string; `required` blocks submission while empty.

```html
<form
  class="flex flex-col items-start gap-4"
  onsubmit="
    event.preventDefault();
    const data = new FormData(event.target);
    alert('description = ' + data.get('description'));
  "
>
  <label class="flex w-full flex-col gap-1">
    <span class="text-sm font-medium">Description</span>
    <l-prose-editor
      name="description"
      required
      toolbar-preset="minimal"
      placeholder="Describe your product…"
    ></l-prose-editor>
  </label>
  <button
    type="submit"
    class="l-button"
    data-variant="primary"
  >
    Submit
  </button>
</form>
```

## Accessibility

### Criteria

- **Toolbar role** — The toolbar uses `role="toolbar"` with an accessible label
- **Button state** — Each toolbar button exposes `aria-pressed` reflecting whether the mark/format is active
- **Button label** — Icon-only buttons carry an `aria-label` and `title` describing the action
- **Focus state** — Visible focus ring on toolbar buttons for keyboard users
- **Form validation** — When `required`, an empty editor reports a `valueMissing` validation message to the form

### Rules

- Associate a visible `<label>` with the editor, or set `aria-label` on the host element
- Keep custom toolbars logically ordered so keyboard users encounter commands in a predictable sequence

### Keyboard interactions

- `Tab` — Moves focus into the toolbar, then into the editable content
- `Ctrl/Cmd + B` — Toggles bold
- `Ctrl/Cmd + I` — Toggles italic
- `Ctrl/Cmd + U` — Toggles underline
- `Ctrl/Cmd + Z` — Undo
- `Ctrl/Cmd + Shift + Z` — Redo
- `Escape` — Closes the emoji picker when open

## API reference

### Importing

Import the element and the content stylesheet once globally. The stylesheet styles the editable area, which renders in light DOM to avoid `contenteditable` caret bugs inside shadow trees.

```js
import 'luxen-ui/prose-editor';
```

```css
@import 'luxen-ui/css/prose-editor';
```

### Attributes & Properties

- **editor**: `Editor` — The Tiptap editor instance. Available after the first render.
- **initial-html**: `string` — Initial HTML content.
- **initial-json**: `string` — Initial content as a serialized ProseMirror JSON string.
- **editor-class**: `string` (default: `'prose'`) — Class applied to the `.ProseMirror` editable element (e.g. for Tailwind Typography `prose`).
- **toolbar**: `ToolbarCommandName[]` (default: `[]`) — Explicit list of toolbar commands. Overrides `toolbar-preset` when set.
- **toolbar-preset**: `'default' | 'minimal'` (default: `'default'`) — Built-in toolbar layout used when `toolbar` is not set.
- **toolbar-placement**: `'top' | 'bottom'` (default: `'top'`) — Where the toolbar sits relative to the content.
- **autofocus**: `boolean` (default: `false`) — Focus the editor on creation.
- **placeholder**: `string` — Placeholder shown when the editor is empty.
- **emoji-data-source**: `string` — URL the emoji picker fetches its data from. Point this at a locally served
`emojibase-data` JSON to run fully offline (no CDN). Defaults to the
picker's bundled CDN source.
- **validationTarget**: `HTMLElement | undefined`

### Methods

- **getHTML()** → `string` — Get the current content as an HTML string. Empty paragraph resolves to `''`.
- **getJSON()** → `JSONContent` — Get the current content as ProseMirror JSON.
- **clear()** — Remove all content.
- **focus()**
- **blur()**
- **toggleBold()**
- **toggleItalic()**
- **toggleUnderline()**
- **toggleStrike()**
- **toggleHighlight()**
- **toggleHeading(level: 1 | 2 | 3)**
- **toggleBulletList()**
- **toggleOrderedList()**
- **toggleBlockquote()**
- **toggleCodeBlock()**
- **setHorizontalRule()**
- **undo()**
- **redo()**
- **toggleLink()**
- **formResetCallback()**

### Events

- **change** — Fired when the content changes. Bubbles. Properties: `html: string`, `json: JSONContent`.
- **add-file** — Fired when the attachment toolbar button is clicked.

### Slots

- **toolbar-start** — Content placed before the generated toolbar buttons.
- **toolbar-end** — Content placed after the generated toolbar buttons.

### CSS Parts

- `wrapper` — The editor frame wrapping the toolbar and content.
- `toolbar` — The toolbar row.
- `toolbar-button` — Any toolbar button.
- `divider` — A toolbar divider.
- `editor` — The container around the editable content.

### CSS custom properties

- `--border-color` — Color of the editor frame border.
- `--border-width` — Width of the editor frame border.
- `--border-radius` — Corner radius of the editor frame.
- `--background` — Background color of the editor.
- `--color` — Text color of the editor.
- `--toolbar-background` — Background color of the toolbar.
- `--toolbar-padding` — Padding around the toolbar.
- `--toolbar-gap` — Gap between toolbar buttons.
- `--toolbar-divider-color` — Color of toolbar dividers.
- `--toolbar-button-size` — Size of toolbar buttons.
- `--toolbar-button-radius` — Corner radius of toolbar buttons.
- `--toolbar-button-color` — Icon color of inactive toolbar buttons.
- `--toolbar-button-color-active` — Icon color of hovered/active toolbar buttons.
- `--toolbar-button-background-hover` — Background of hovered toolbar buttons.
- `--toolbar-button-background-active` — Background of active toolbar buttons.
- `--content-padding` (default: `0.75rem 1rem`) — Padding inside the editable content region.
- `--content-min-height` (default: `8rem`) — Minimum height of the editable content region.
- `--placeholder-color` — Placeholder text color.
