# Rich Text Editor

> Formatted text for newsletters, product descriptions, legal pages and canned replies: toolbar, placeholder fields and a character count.

Component · Forms · Pro kit · [Live page](https://ui.twentyfirst.media/pro/components/RichTextEditor/)

Formatted text for newsletters, product descriptions, legal pages and canned replies: toolbar, placeholder fields and a character count.

Props: `label`, `defaultValue` (HTML), `onChange(html)`, `placeholder`, `variables` ({key, label}: inserted as `{key}`), `maxLength`, `minHeight`, `footer`, `hint`, `error`, `disabled`.

- The toolbar keeps to bold, italic, underline, heading, quote, lists, link, undo/redo. Anything richer belongs in a dedicated editor.
- Built on Selection/Range, no `execCommand`. Shortcuts: ⌘/Ctrl B, I, U, K (link), Z and ⇧Z (undo, redo).
- Links open a small field instead of a browser prompt; addresses without a scheme get https://.
- Pasting inserts plain text; `onChange` returns HTML limited to p, h3, blockquote, ul, ol, li, b, i, u, a and br.
- Still sanitize on the server before you send or store it.

## Example

```tsx
import { RichTextEditor } from "@twentyfirstmedia/pro";

export default function Preview() {
  return (
    <div className="tf-panel">
      <RichTextEditor
        label="Newsletter text"
        placeholder="Write something…"
        maxLength={2000}
        defaultValue="<h3>Your boost is live</h3><p>Hi {first_name}, your <b>1,000 followers</b> start arriving within the next hour.</p><ul><li>Delivery spreads over 24 h</li><li>Refill guarantee for 30 days</li></ul>"
        variables={[
          {
            key: "first_name",
            label: "First name",
          },
          {
            key: "order_id",
            label: "Order number",
          },
          {
            key: "coupon",
            label: "Coupon code",
          },
        ]}
        hint="Fields in braces are filled per recipient."
      />
    </div>
  );
}
```

## Props

### RichTextEditor

| Prop | Type | Notes |
| --- | --- | --- |
| `label?` | `ReactNode` |  |
| `defaultValue?` | `string` |  |
| `value?` | `string` |  |
| `onChange?` | `(html: string) => void` |  |
| `placeholder?` | `string` |  |
| `variables?` | `{ key: string; label: string }[]` |  |
| `maxLength?` | `number` |  |
| `minHeight?` | `number` |  |
| `footer?` | `ReactNode` |  |
| `hint?` | `ReactNode` |  |
| `error?` | `ReactNode` |  |
| `disabled?` | `boolean` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |
