# Reetesh Kumar — full content export
> Machine-readable export from https://iamreetesh.com. Lextrix version 2.0.5. Canonical monorepo docs: https://github.com/rishureetesh/lextrix/tree/main/docs.
---
# Lextrix documentation
## Getting Started
Source: https://iamreetesh.com/docs/getting-started
**Try it:** [Live playground](https://iamreetesh.com/lextrix) — themes, import/export, and all modules in the browser.
New here? Follow the [Evaluation guide](/docs/evaluation) for a linear install → export walkthrough.
## Install
```bash
npm install lextrix
```
```javascript
import Lextrix from 'lextrix';
import 'lextrix/snow.css';
```
| Import | Purpose |
|--------|---------|
| `lextrix` | Full editor — ESM (`lextrix.esm.js`) for bundlers, UMD (`lextrix.js`) for script tags |
| `lextrix/core` | Core-only bundle (no formats/themes) |
| `lextrix/lextrix.css` | Base editor styles |
| `lextrix/snow.css` | Snow theme |
| `lextrix/bubble.css` | Bubble theme |
| `lextrix/slate.css` | Dark slate theme |
| `lextrix/dawn.css` | Warm dawn theme |
Named imports (`ChangeSet`, `registerSerializer`, `lxrPath`, …) work from `lextrix` in bundlers (2.0.1+). Script-tag UMD exposes `window.Lextrix` only.
Optional peer dependencies: [highlight.js](https://highlightjs.org/) for syntax, [KaTeX](https://katex.org/) for formulas.
**React / Next.js:** [`@lextrix/react`](/docs/react) · [Frameworks](/docs/frameworks) · [Evaluation](/docs/evaluation) · [Cookbook](/docs/cookbook)
Script-tag setup (npm paths or CDN): [Framework integration — Script tag](/docs/frameworks#script-tag-no-bundler).
## Snow theme
```html
```
```javascript
const editor = new Lextrix('#editor', {
theme: 'snow',
placeholder: 'Start writing…',
modules: {
toolbar: [
[{ header: [1, 2, false] }],
['bold', 'italic', 'underline', 'strike'],
[{ list: 'ordered' }, { list: 'bullet' }],
['link', 'image'],
['clean'],
],
imageResize: true,
},
});
editor.setContents([
{ insert: 'Hello Lextrix\n', attributes: { header: 1 } },
{ insert: 'Select an image and drag the corner handle to resize.\n' },
]);
```
## Bubble theme
```javascript
const editor = new Lextrix('#editor', {
theme: 'bubble',
modules: {
toolbar: [
['bold', 'italic', 'link'],
[{ header: 1 }, { header: 2 }],
],
},
});
```
## Read-only
```javascript
const editor = new Lextrix('#editor', {
theme: 'snow',
readOnly: true,
});
```
## Getting content
```javascript
const json = editor.getContents(); // ChangeSet
const html = editor.exportContent('html');
const text = editor.getText();
```
## Listening to changes
```javascript
editor.on('text-change', (delta, oldDelta, source) => {
if (source === 'user') {
save(editor.getContents());
}
});
```
See [Configuration](/docs/configuration) for modules, upload handlers, and format whitelists.
---
## Evaluation Guide
Source: https://iamreetesh.com/docs/evaluation
A linear path for trying Lextrix for the first time. No setup beyond a bundler or static HTML page.
**Using React or Next.js?** Prefer [`@lextrix/react`](/docs/react). See [Frameworks](/docs/frameworks) after step 5.
---
## 1. Try the playground
Open the [live playground](https://iamreetesh.com/lextrix) in your browser. No install required — edit content, switch themes, and try import/export panels.
---
## 2. Install Lextrix
```bash
npm install lextrix
```
Install only the **`lextrix`** package. Everything you need for a full editor ships in that bundle.
---
## 3. Import CSS
Lextrix does not inject styles. Import a theme in your app entry:
```javascript
import 'lextrix/snow.css';
```
Other themes: `lextrix/bubble.css`, `lextrix/slate.css`, `lextrix/dawn.css`.
---
## 4. Create an editor
```html
```
```javascript
import Lextrix from 'lextrix';
import 'lextrix/snow.css';
const editor = new Lextrix('#editor', {
theme: 'snow',
placeholder: 'Start writing…',
modules: {
toolbar: [
['bold', 'italic', 'underline'],
[{ header: [1, 2, false] }],
[{ list: 'ordered' }, { list: 'bullet' }],
['link', 'clean'],
],
},
});
```
You should see a toolbar and an editable area immediately.
---
## 5. Load content
**From ChangeSet JSON** (same shape as `getContents()`):
```javascript
editor.setContents([
{ insert: 'Hello Lextrix\n', attributes: { header: 1 } },
{ insert: 'Edit rich text in the browser.\n' },
]);
```
**From Markdown or HTML:**
```javascript
editor.importContent('# Title\n\n**Bold** paragraph.', 'markdown');
editor.importContent('Hello world
', 'html');
```
---
## 6. Export content
```javascript
const markdown = editor.exportContent('markdown');
const html = editor.exportContent('html');
const json = editor.getContents(); // ChangeSet
```
**Before Markdown or MDX export**, check for unsupported or lossy content:
```javascript
const warnings = editor.getExportWarnings('markdown');
for (const w of warnings) {
console.warn(w.message);
}
const md = editor.exportContent('markdown');
```
Native editor tables **cannot** export to Markdown/MDX — export throws `SerializationError`. Use `exportContent('html')` instead. See [Serialization](/docs/serialization) for the full limitations list.
---
## 7. Image resize
Enable the opt-in **`imageResize`** module and include **`image`** in the toolbar (or insert an image programmatically). Select a single image — a corner handle appears; drag to resize. Aspect ratio is preserved.
```javascript
const editor = new Lextrix('#editor', {
theme: 'snow',
modules: {
toolbar: [
['bold', 'italic'],
['link', 'image'],
['clean'],
],
imageResize: {
minWidth: 48,
maxWidth: null, // null = editor width
},
},
});
// Insert via toolbar, drag-and-drop, or API:
editor.insertEmbed(0, 'image', 'https://example.com/photo.jpg');
```
**Programmatic resize** (sets the same `width` / `height` attributes as the handle):
```javascript
editor.formatText(imageIndex, 1, { width: '400', height: '300' }, 'user');
```
Does not apply to video or other embeds. Details: [Configuration — Image resize](/docs/configuration#image-resize) · [API reference — imageResize](/docs/commands#image-resize-imageresize)
---
## 8. Optional modules
These features are opt-in or configured via `modules`. Full API: [API reference — Modules](/docs/commands#modules).
### Undo / redo
Always available via `editor.history`. Keyboard: `Ctrl+Z` / `Ctrl+Shift+Z`.
```javascript
editor.history.undo();
editor.history.redo();
```
### Image upload
Override the default base64 handler to upload to your server:
```javascript
modules: {
toolbar: [['bold', 'italic'], ['link', 'image'], ['clean']],
uploader: {
mimetypes: ['image/png', 'image/jpeg', 'image/webp'],
async handler(range, files) {
for (const file of files) {
const body = new FormData();
body.append('file', file);
const res = await fetch('/api/upload', { method: 'POST', body });
const { url } = await res.json();
this.lextrix.insertEmbed(range.index, 'image', url, 'user');
range.index += 1;
}
},
},
}
```
Toolbar **image** and drag-and-drop both use this handler.
### Syntax highlighting
Load [highlight.js](https://highlightjs.org/) first, then enable the module:
```javascript
modules: {
toolbar: [['bold', 'italic'], ['code-block'], ['clean']],
syntax: {
hljs: window.hljs,
languages: [{ key: 'javascript', label: 'JavaScript' }],
},
}
```
### Tables
```javascript
modules: {
toolbar: [['bold', 'italic'], ['table'], ['clean']],
table: true,
}
editor.getModule('table')?.insertTable(3, 4);
```
Remember: native tables cannot export to Markdown/MDX — use HTML export.
---
## 9. React / Next.js next steps
Prefer the official wrapper:
```bash
npm install lextrix @lextrix/react
```
- [React guide](/docs/react) — `LextrixEditor` (recommended)
- [Frameworks](/docs/frameworks) — Next.js, Vue, script tag, manual mount
- [DOM mounting](/docs/dom-mounting) — if you mount Lextrix yourself
- Examples: [vite-vanilla](https://github.com/rishureetesh/lextrix/tree/main/examples/vite-vanilla) · [vite-react](https://github.com/rishureetesh/lextrix/tree/main/examples/vite-react)
---
## 10. Troubleshooting
| Symptom | Fix |
|---------|-----|
| Unstyled editor | Import `lextrix/snow.css` (or another theme) |
| `document is not defined` | Client-only — see [Frameworks](/docs/frameworks) |
| Duplicate toolbars | Call `editor.destroy()` on unmount — [DOM mounting](/docs/dom-mounting) |
| Markdown export throws | Native table in document — use HTML export or check [Serialization](/docs/serialization) |
| No image resize handle | Enable `modules.imageResize` and select a **single** image — [Image resize](/docs/configuration#image-resize) |
| Code blocks not highlighted | Load highlight.js and enable `modules.syntax` — [API reference](/docs/commands#syntax-highlighting-syntax) |
| Upload does nothing | Check `uploader.mimetypes` matches file type — [API reference](/docs/commands#uploader-uploader) |
More recipes: [Cookbook](/docs/cookbook) · Full API: [API reference](/docs/commands)
---
## What to read next
| Goal | Guide |
|------|-------|
| Copy-paste recipes | [Cookbook](/docs/cookbook) |
| Save on change, uploads, tables | [Cookbook](/docs/cookbook) |
| Image resize | [Configuration — Image resize](/docs/configuration#image-resize) · [API reference](/docs/commands#image-resize-imageresize) |
| Upload, syntax, tables | [Optional modules](#8-optional-modules) · [API reference — Modules](/docs/commands#modules) |
| Custom serializers | [API reference — registerSerializer](/docs/commands#registerserializerserializer) |
| All import/export formats | [Serialization](/docs/serialization) |
| Module and toolbar options | [Configuration](/docs/configuration) |
---
## Cookbook
Source: https://iamreetesh.com/docs/cookbook
# Cookbook
Copy-paste recipes for common tasks. Written for application developers integrating the **npm** package.
**Start here if toolbars duplicate or themes break:** [DOM mounting](/docs/dom-mounting).
---
## 1. Minimal editor
```html
```
```javascript
import Lextrix from 'lextrix';
import 'lextrix/snow.css';
const wrapper = document.getElementById('wrapper');
const mount = document.createElement('div');
wrapper.appendChild(mount);
const editor = new Lextrix(mount, {
theme: 'snow',
placeholder: 'Write here…',
});
editor.setContents([{ insert: 'Hello\n' }]);
```
---
## 2. Toolbar
```javascript
new Lextrix(mount, {
theme: 'snow',
modules: {
toolbar: [
['bold', 'italic', 'underline', 'strike'],
[{ header: [1, 2, 3, false] }],
[{ list: 'ordered' }, { list: 'bullet' }],
['link', 'image'],
['clean'],
],
},
});
```
Use your own toolbar DOM:
```javascript
modules: { toolbar: '#my-toolbar' }
```
Button names: see [Configuration — Toolbar](/docs/configuration#toolbar-buttons).
---
## 3. Save on change
```javascript
editor.on('text-change', (delta, oldDelta, source) => {
if (source === 'user') {
const json = editor.getContents();
localStorage.setItem('draft', JSON.stringify(json));
}
});
```
`source` is `'user'`, `'api'`, or `'silent'`. Ignore `'api'` if you do not want programmatic updates to trigger saves.
---
## 4. Load saved content
```javascript
const saved = localStorage.getItem('draft');
if (saved) {
editor.setContents(JSON.parse(saved));
}
```
Or import from Markdown/HTML — [Serialization](/docs/serialization).
---
## 5. Read-only display
```javascript
new Lextrix(mount, {
theme: 'snow',
readOnly: true,
});
editor.setContents(documentJson);
```
---
## 6. Serialization (Markdown, HTML, MDX, JSON)
```javascript
// Export
const md = editor.exportContent('markdown');
const html = editor.exportContent('html');
// Import (replaces document)
editor.importContent('# Title\n\n**bold**', 'markdown');
editor.importContent('Hello
', 'html');
// Slice
const slice = editor.exportContent({ format: 'html', index: 0, length: 50 });
```
Tables: native editor tables cannot export to Markdown/MDX — use HTML. Details: [Serialization](/docs/serialization).
---
## 7. Undo / redo
Keyboard: `Ctrl+Z` / `Ctrl+Shift+Z` (focus must be in the editor).
```javascript
editor.history.undo();
editor.history.redo();
editor.history.clear(); // new document / discard stack
```
Programmatic changes use `source: 'api'` by default and are undoable unless `history.userOnly` is true.
---
## 8. Image upload to your server
Default: base64 in document. Override uploader:
```javascript
modules: {
uploader: {
mimetypes: ['image/png', 'image/jpeg', 'image/webp'],
async handler(range, files) {
for (const file of files) {
const body = new FormData();
body.append('file', file);
const res = await fetch('/api/upload', { method: 'POST', body });
const { url } = await res.json();
this.lextrix.insertEmbed(range.index, 'image', url, 'user');
range.index += 1;
}
},
},
}
```
Toolbar **image** button and drag-and-drop both use this handler.
---
## 9. Image resize
```javascript
modules: { imageResize: { minWidth: 48 } }
```
Select an image in the editor — drag the corner handle.
---
## 10. Syntax highlighting
Load [highlight.js](https://highlightjs.org/) on the page first.
```javascript
modules: {
syntax: {
hljs: window.hljs,
languages: [
{ key: 'javascript', label: 'JavaScript' },
{ key: 'python', label: 'Python' },
],
},
}
```
---
## 11. Tables
```javascript
modules: { table: true }
editor.getModule('table').insertTable(3, 4); // rows, columns
```
---
## 12. Switch theme at runtime
See [Themes](/docs/themes). Summary:
1. `getContents()`
2. Swap CSS
3. `wrapper.replaceChildren()`
4. New mount + `new Lextrix`
5. `setContents()`
---
## 13. React / Next.js
Full guide: [Framework integration](/docs/frameworks).
Rules:
- `'use client'` + `dynamic(..., { ssr: false })` in Next.js
- **Wrapper ref** + inner mount div
- `import Lextrix from 'lextrix'` (default import)
- Never `` in JSX
---
## 14. Custom formats (npm)
```javascript
import Lextrix, { lxrPath } from 'lextrix';
class CalloutBlot {
// blot implementation …
}
Lextrix.register({ [lxrPath.format('callout')]: CalloutBlot });
```
`defineInlineTagFormat` and similar helpers need the monorepo. See [Formats](/docs/plugins).
---
## 15. Custom serializers
```javascript
import { registerSerializer } from 'lextrix';
import { ChangeSet } from 'lextrix';
registerSerializer({
format: 'plain',
import: (text) => new ChangeSet().insert(text).insert('\n'),
export: (delta) => delta.getText(),
});
```
Call **before** `new Lextrix()`.
---
## 16. Check export warnings (Markdown / MDX)
```javascript
const warnings = editor.getExportWarnings('markdown');
for (const w of warnings) {
console.warn(w.message); // tables blocked, color/align lossy, etc.
}
const md = editor.exportContent('markdown'); // throws if native table present
```
---
## Troubleshooting
| Symptom | Likely cause | Fix |
|---------|--------------|-----|
| Multiple toolbars | Old 2.0.0 sibling toolbar, or no `destroy()` on remount | Upgrade to 2.0.1+; call `editor.destroy()` |
| `Element type is invalid… undefined` | React: wrong import or `` | [Frameworks](/docs/frameworks) |
| `document is not defined` | SSR / Server Component | Client-only + `ssr: false` |
| Unstyled editor | Theme CSS not imported | `import 'lextrix/snow.css'` |
| Undo does nothing | Editor not focused | `wrapper.click()` or `editor.focus()` |
| `{ ChangeSet }` undefined (2.0.0) | Old UMD-only build | Upgrade to **2.0.1+** |
---
## API index
| Task | Methods |
|------|---------|
| Content | `getContents`, `setContents`, `getText`, `updateContents` |
| Format | `format`, `formatText`, `removeFormat` |
| Selection | `getSelection`, `setSelection`, `focus` |
| Events | `on('text-change')`, `on('selection-change')` |
| Serialize | `importContent`, `exportContent` |
Full list: [API reference](/docs/commands).
---
## React Integration
Source: https://iamreetesh.com/docs/react
# React integration
Use **`@lextrix/react`** for a typed component with correct mount/teardown. Install **both** packages:
```bash
npm install lextrix @lextrix/react react react-dom
```
Import a theme stylesheet (required):
```ts
import 'lextrix/snow.css';
```
## `LextrixEditor`
```tsx
'use client'; // Next.js App Router only
import { useState } from 'react';
import { LextrixEditor } from '@lextrix/react';
import 'lextrix/snow.css';
export function BlogEditor() {
const [body, setBody] = useState('# Draft\n');
return (
);
}
```
### Props
| Prop | Description |
|------|-------------|
| `theme` | `snow`, `bubble`, `slate`, `dawn` — changing remounts the editor |
| `options` | `LextrixOptions` (minus `theme`); applied on **mount only** |
| `value` / `defaultValue` | Document string in `format` |
| `format` | `html` \| `markdown` \| `mdx` \| `json` (default `html`) |
| `onChange` | `(content, source) => void` after user edits |
| `onSelectionChange` | Lextrix selection event |
| `onReady` | `(editor) => void` once after create |
| `className` / `style` | Wrapper element |
### Ref handle
```tsx
import { useRef } from 'react';
import type { LextrixEditorHandle } from '@lextrix/react';
const ref = useRef(null);
e.focus()} />;
ref.current?.exportContent('markdown');
ref.current?.getEditor()?.getModule('history')?.undo();
```
## Remounting when options change
`options` are not deep-watched. Remount with `key` when modules or placeholder change:
```tsx
```
## Next.js
- Component ships with `'use client'`
- Do not import `lextrix` or `@lextrix/react` in Server Components
- Dynamic import optional if you need to defer CSS:
```tsx
import dynamic from 'next/dynamic';
const LextrixEditor = dynamic(
() => import('@lextrix/react').then((m) => m.LextrixEditor),
{ ssr: false },
);
```
## Manual integration (no wrapper)
See [Framework integration](/docs/frameworks#react-vite-cra-etc) for the wrapper-ref pattern behind this component.
## Example app
```bash
npm run build
npm run example:react
```
Source: [`examples/vite-react`](https://github.com/rishureetesh/lextrix/tree/main/examples/vite-react).
---
## Framework Integration
Source: https://iamreetesh.com/docs/frameworks
Lextrix is a **DOM-based editor**. Vanilla apps call `new Lextrix(container, options)`. React apps should use **[`@lextrix/react`](/docs/react)** (`LextrixEditor`).
## npm package layout
| Package / import | When to use |
|------------------|-------------|
| `lextrix` | Core editor (required for everyone) |
| `@lextrix/react` | Official React component — install **with** `lextrix` |
| `import Lextrix from 'lextrix'` | Bundlers resolve the **ESM** build |
| `import { ChangeSet, registerSerializer, lxrPath } from 'lextrix'` | Named APIs |
| `import 'lextrix/snow.css'` | Theme stylesheet (required) |
| Script tag `lextrix.js` | UMD global `window.Lextrix` |
Always import a theme CSS file. The editor does not inject styles.
### npm vs monorepo
| Need | npm | Monorepo (clone repo) |
|------|-----|------------------------|
| Editor + themes + serialization | `lextrix` | Same |
| React component | `lextrix` + `@lextrix/react` | `packages/react` |
| `ChangeSet`, `registerSerializer`, `lxrPath` | `import { … } from 'lextrix'` | Same |
| Custom blots | `Lextrix.register()` | Or import from `lextrix-formats` |
| Headless serialize only | Editor APIs, or clone for `lextrix-serialize` | `lextrix-serialize` |
Lextrix requires a browser (`document`). Do not import it in Server Components without a client-only boundary.
**Toolbar / remount bugs?** Read [DOM mounting](/docs/dom-mounting) — call `editor.destroy()` (or use `@lextrix/react`, which does this for you).
---
## React
**Recommended:** [`@lextrix/react`](/docs/react)
```bash
npm install lextrix @lextrix/react
```
```tsx
import { LextrixEditor } from '@lextrix/react';
import 'lextrix/snow.css';
;
```
### Manual mount (advanced)
Only if you cannot use `@lextrix/react`. Use a **wrapper ref**, a **fresh inner div**, and `destroy()` on teardown.
```tsx
'use client'; // Next.js App Router only
import { useEffect, useRef } from 'react';
import Lextrix from 'lextrix';
import 'lextrix/snow.css';
export default function ManualEditor() {
const wrapperRef = useRef(null);
useEffect(() => {
const wrapper = wrapperRef.current;
if (!wrapper) return;
const mount = document.createElement('div');
wrapper.appendChild(mount);
const editor = new Lextrix(mount, {
theme: 'snow',
placeholder: 'Start writing…',
modules: {
toolbar: [
['bold', 'italic', 'underline'],
[{ header: [1, 2, false] }],
[{ list: 'ordered' }, { list: 'bullet' }],
['link', 'clean'],
],
},
});
editor.setContents([{ insert: 'Hello Lextrix\n' }]);
return () => {
editor.destroy();
wrapper.replaceChildren();
};
}, []);
return ;
}
```
**Do not** render `` — `Lextrix` is a class, not a React component.
For theme switching and controlled Markdown, prefer [`LextrixEditor`](/docs/react) with a remount `key` instead of hand-rolling effects.
See [Themes](/docs/themes) · [React guide](/docs/react).
---
## Next.js App Router
Use `@lextrix/react` from a **client** component (it includes `'use client'`):
```tsx
'use client';
import { LextrixEditor } from '@lextrix/react';
import 'lextrix/snow.css';
export default function PageEditor() {
return ;
}
```
Optional lazy load:
```tsx
import dynamic from 'next/dynamic';
const LextrixEditor = dynamic(
() => import('@lextrix/react').then((m) => m.LextrixEditor),
{ ssr: false },
);
```
Do not import `lextrix` or `@lextrix/react` in Server Components.
---
## Vue 3
```vue
```
---
## Script tag (no bundler)
Use the published package from **npm** or a **CDN**. After `npm install lextrix`, files are at the package root (e.g. `node_modules/lextrix/lextrix.snow.css`).
**From npm (local static server):**
```html
```
**From jsDelivr CDN** (pin the version you use):
```html
```
UMD exposes `window.Lextrix` only. Named exports (`ChangeSet`, `registerSerializer`, …) require a bundler and the ESM entry (`import Lextrix from 'lextrix'`).
---
## Migrating from Quill
| Quill | Lextrix |
|-------|---------|
| `new Quill(el, opts)` | `new Lextrix(el, opts)` |
| `quill.getContents()` (Delta) | `editor.getContents()` (ChangeSet) |
| `quill.root.innerHTML` | `editor.exportContent('html')` |
| `import 'quill/dist/quill.snow.css'` | `import 'lextrix/snow.css'` |
| Delta | **ChangeSet** (same JSON shape, different name) |
See [change-set.md](https://github.com/rishureetesh/lextrix/blob/main/docs/guides/change-set.md) and [serialization.md](/docs/serialization).
---
## TypeScript
Source: https://iamreetesh.com/docs/typescript
# TypeScript
The published `lextrix` npm package includes hand-written declarations (`lextrix.d.ts`).
## Setup
```bash
npm install lextrix
```
```typescript
import Lextrix, { ChangeSet, lxrPath } from 'lextrix';
import type {
LextrixOptions,
LextrixModulesConfig,
ImageResizeOptions,
TableModule,
ContentSerializer,
SafetyIssue,
Range,
} from 'lextrix';
import 'lextrix/snow.css';
const modules: LextrixModulesConfig = {
toolbar: [['bold', 'italic'], ['link', 'image']],
table: true,
imageResize: { minWidth: 48 } satisfies ImageResizeOptions,
syntax: { hljs: globalThis.hljs, languages: [{ key: 'javascript', label: 'JS' }] },
};
const options: LextrixOptions = { theme: 'snow', modules };
const editor = new Lextrix('#editor', options);
const table = editor.getModule('table') as TableModule | undefined;
table?.insertTable(3, 3);
```
No `@types/lextrix` package is required.
## Covered APIs
- `Lextrix` class and `LextrixOptions`
- Module config types: `LextrixModulesConfig`, `ImageResizeOptions`, `ToolbarModuleOptions`, `SyntaxModuleOptions`, `UploaderModuleOptions`, `HistoryModuleOptions`, `ClipboardModuleOptions`
- `TableModule` and `getModule('table')`
- `ImageResizeModule` and `getModule('imageResize')`
- `setText`, `getLeaf`, `getLine`, `getLines`, `getIndex`, `scrollSelectionIntoView`
- `importContent`, `exportContent`, `listExportFormats`, `getExportWarnings`
- `text-change` and `selection-change` handler signatures
- `ChangeSet`, `Range`, `SafetyIssue`, `ContentSerializer`, serialization types
- Named exports: `ChangeSet`, `lxrPath`, `registerSerializer`, `getMarkdownExportWarnings`, …
## Gaps
Declarations are maintained manually and may lag new APIs. Report missing types on [GitHub Issues](https://github.com/rishureetesh/lextrix/issues).
Advanced blot types (`Block`, `LeafBlot`, …) are not exported from the npm bundle types — use `unknown` or clone the monorepo for full internal typings.
## Example
See [examples/vite-react](https://github.com/rishureetesh/lextrix/tree/main/examples/vite-react) for a typed React + Vite project.
---
## DOM Mounting
Source: https://iamreetesh.com/docs/dom-mounting
# DOM mounting (read this first)
Lextrix is not a single DOM node. Understanding the layout prevents duplicate toolbars and leaked listeners.
## What Lextrix creates
When you call `new Lextrix(mountElement, options)`:
1. **`mountElement`** becomes `editor.container` (class `lxr-container`).
2. If `modules.toolbar` is an **array**, Lextrix adds **`div.lxr-toolbar`** as the **first child** of `mountElement` (since 2.0.1).
3. Inside `mountElement`, **`div.lxr-editor`** holds the editable surface.
```
div (mountElement — passed to new Lextrix)
├── div.lxr-toolbar ← when toolbar is a config array
└── div.lxr-editor ← editable area
```
Older 2.0.0 inserted the toolbar as a **sibling before** the mount node. Upgrade to **2.0.1+** if you still see stacked toolbars outside the mount.
## Cleanup
### Preferred: `editor.destroy()`
```javascript
const editor = new Lextrix(mount, { theme: 'snow', modules: { toolbar: [...] } });
// theme switch, route leave, React unmount
editor.destroy();
```
`destroy()` removes the auto-created toolbar, clears theme `document.body` listeners, drops emitter handlers, and runs `mountElement.replaceChildren()`.
### Manual: clear the mount element
```javascript
mount.replaceChildren(); // removes toolbar + editor inside mount (2.0.1+)
```
### React wrapper pattern
Still use an outer **wrapper** you control — especially when swapping themes or remounting:
```tsx
const wrapperRef = useRef(null);
const editorRef = useRef(null);
useEffect(() => {
const wrapper = wrapperRef.current;
if (!wrapper) return;
const mount = document.createElement('div');
wrapper.appendChild(mount);
const editor = new Lextrix(mount, { theme, modules: { toolbar: [...] } });
editorRef.current = editor;
return () => {
editor.destroy();
editorRef.current = null;
wrapper.replaceChildren();
};
}, [theme]);
```
## Custom toolbar element
| `modules.toolbar` | Toolbar location |
|-------------------|------------------|
| Array `[['bold'], …]` | Auto `div.lxr-toolbar` inside mount |
| `'#my-toolbar'` | Your existing element |
| `{ container: HTMLElement }` | Your element |
You manage cleanup for custom toolbar DOM.
## Theme CSS
Load one theme CSS file (`import 'lextrix/snow.css'`). When switching themes at runtime, swap CSS then `destroy()` + recreate. See [Themes](/docs/themes).
## Peer dependencies
| Feature | Requires | If missing (2.0.1+) |
|---------|----------|---------------------|
| Formula button | [KaTeX](https://katex.org/) on `window.katex` | Button hidden; paste/import shows LaTeX source |
| Syntax highlighting | [highlight.js](https://highlightjs.org/) | Module no-ops; code blocks work unhighlighted |
## Markdown export warnings
Before `exportContent('markdown'|'mdx')`:
```javascript
const warnings = editor.getExportWarnings('markdown');
// e.g. native tables (blocked), color/align (lossy)
```
Native editor tables still **throw** on export — use HTML or check warnings first.
---
## Serialization
Source: https://iamreetesh.com/docs/serialization
Import and export editor content as HTML, Markdown, MDX, or JSON. All formats go through **ChangeSet** — the same model as `getContents()`, clipboard, and OT.
There is no direct HTML ↔ Markdown ↔ MDX conversion. To change formats, import into the editor (or `SerializerHost`) and export the target format.
## Quick start
```typescript
import Lextrix from 'lextrix';
const editor = new Lextrix('#editor');
const html = editor.exportContent('html');
const md = editor.exportContent('markdown');
editor.importContent('Hello world
', 'html');
editor.importContent('# Title\n\nParagraph.', 'markdown');
```
Use `importContent` / `exportContent` instead of `import` / `export`. `Lextrix.import()` is the module loader and is unrelated.
## Limitations
| Topic | Behavior |
|-------|----------|
| Native editor tables → Markdown/MDX | Throws `SerializationError` (`TABLE_EXPORT_UNSUPPORTED`). Use `exportContent('html')`. |
| GFM tables (Markdown import) | Simple tables round-trip. No colspan, rowspan, or merged cells. |
| HTML round-trip | Partial. Export uses `getSemanticHTML()`; re-import normalizes DOM structure. |
| Markdown dialect | Subset only — not full CommonMark/GFM. |
| MDX components | Experimental. JSX preserved as text unless you register handlers. |
| Ordered lists | Numbering is sequential (`1.`, `2.`, …); nested lists restart at `1.` per indent level |
| Combined bold+italic | May export as bold only |
| Align, color, font | HTML/JSON only — not exported to Markdown/MDX |
| Video embed | Markdown/MDX export as \`[video](url)\` |
| Horizontal rule | Literal `---` paragraph exports as `\-\-\-` so it does not become a thematic break |
| Inline escaping | Only `\*`, `` \` ``, `\_`, `\[`, `\]` — not `.` or `!` at end of sentences |
### MDX components (experimental)
`registerMdxComponent` is not stable API.
- Unknown JSX is stored as raw text with an `mdx-component` block attribute
- Frontmatter and `import`/`export` lines are stripped on import
- Parsing uses regex, not an AST — nested or complex JSX will miss edge cases
- No component embed blot, in-editor preview, or JSX runtime
- Use MDX export for CMS storage; register `toChangeSet` / `fromChangeSet` only for components you control and test
## API
### Editor
```typescript
editor.exportContent(format: string): string
editor.exportContent({ format, index?, length? }): string
editor.importContent(content: string, format: string, source?: EmitterSource): void
editor.listExportFormats(): string[]
```
`export()` and `import()` are aliases. Prefer `exportContent` / `importContent` in application code.
### Global registration
```typescript
import { registerSerializer, registerMdxComponent } from 'lextrix';
import ChangeSet from 'lextrix-change';
registerSerializer({
format: 'plain',
import: (text) => new ChangeSet([{ insert: text }, { insert: '\n' }]),
export: (delta) => delta.getText(),
});
```
Call `registerSerializer` before creating editors. Serializers registered globally are merged into each editor unless `serializers: false`.
### Headless (`lextrix-serialize`)
Monorepo / contributors only — the npm package bundles serialization into `lextrix`; use `editor.importContent` / `exportContent` in apps.
```typescript
import {
SerializerHost,
createSerializerRegistry,
markdownSerializer,
} from 'lextrix-serialize';
const host = new SerializerHost(createSerializerRegistry([markdownSerializer()]));
const delta = host.parse('# Hello', 'markdown');
const md = host.stringify(delta, 'markdown');
```
`host.export()` needs a bound editor adapter. For headless export, use `host.stringify()`.
### `LextrixOptions.serializers`
| Value | Effect |
|-------|--------|
| omitted or `true` | Built-in formats (json, html, markdown, mdx) plus globals |
| `ContentSerializer[]` | Listed serializers plus globals |
| `false` | No serializers, including globals |
## Examples
### Partial export
```typescript
const slice = editor.exportContent({ format: 'html', index: 0, length: 100 });
```
### React / Next.js
Prefer **[`@lextrix/react`](/docs/react)** so mount/teardown and `format` are handled for you:
```tsx
import { LextrixEditor } from '@lextrix/react';
import 'lextrix/snow.css';
save(mdx)}
/>
```
Manual mount pattern: [Frameworks](/docs/frameworks) · [DOM mounting](/docs/dom-mounting).
### Migration from older patterns
| Before | After |
|--------|-------|
| `editor.getSemanticHTML()` | `editor.exportContent('html')` |
| `clipboard.convert({ html })` + `setContents` | `editor.importContent(html, 'html')` |
| `JSON.stringify(editor.getContents().ops)` | `editor.exportContent('json')` |
## How it works
```
HTML / Markdown / MDX / JSON
↓ import
ChangeSet
↓ export
HTML / Markdown / MDX / JSON
```
| Package | Role |
|---------|------|
| `lextrix-change` | ChangeSet ops |
| `lextrix-modules/html-import` | HTML string → ChangeSet (shared with clipboard paste) |
| `lextrix-core` | Editor API, HTML export via blot tree |
| `lextrix-serialize` | Markdown, MDX, JSON ↔ ChangeSet; registry; host |
`lextrix-serialize` does not depend on `lextrix-core`. HTML export is DOM-backed, not derived purely from ChangeSet.
## Testing
```bash
npm run test -w lextrix-serialize
npm run test -w lextrix-core
npm run test:unit -w lextrix
```
Tests: `packages/serialize/tests/`, `packages/lextrix/test/unit/core/serialization.spec.ts`, `packages/lextrix/test/e2e/serialization.spec.ts`.
---
## API Reference
Source: https://iamreetesh.com/docs/commands
# API reference
Lextrix exposes a familiar rich-text API centered on **ChangeSet** (operational transforms over document content).
## Construction
```javascript
const editor = new Lextrix(container, options);
```
Static properties: `Lextrix.version`, `Lextrix.events`, `Lextrix.sources`, `Lextrix.import()`, `Lextrix.register()`.
### Lifecycle
| Method | Description |
|--------|-------------|
| `destroy()` | Remove toolbar, theme listeners, and editor DOM inside the mount container |
| `getExportWarnings(input)` | Lossy/unsupported issues before Markdown/MDX export (does not throw) |
See [DOM mounting](/docs/dom-mounting).
### Editor options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `theme` | `string` | `'snow'` | `snow` \| `bubble` \| `slate` \| `dawn` |
| `placeholder` | `string` | `''` | Shown when the document is empty |
| `readOnly` | `boolean` | `false` | Disable editing (`editor.enable()` / `disable()`) |
| `bounds` | `HTMLElement` \| `string` \| `null` | `null` | Root for tooltip positioning |
| `formats` | `string[]` \| `null` | `null` | Whitelist of format names; `null` = all registered |
| `modules` | `object` | `{}` | Module configuration — see [Modules](#modules) |
| `debug` | `boolean` \| `string` | `false` | Log level for development |
```javascript
new Lextrix('#editor', {
theme: 'snow',
placeholder: 'Write here…',
readOnly: false,
modules: { toolbar: [['bold', 'italic']], table: true },
});
```
## Serialization
Import and export whole documents or slices. Prefer **`importContent`** / **`exportContent`** in application code — not `Lextrix.import()`, which loads internal modules.
Full limitations (tables, lossy formats): [Serialization guide](/docs/serialization).
### `importContent(content, format, source?)`
| | |
|---|---|
| **Parameters** | `content` — string to parse · `format` — `'html'` \| `'markdown'` \| `'mdx'` \| `'json'` · `source` — optional `'user'` \| `'api'` \| `'silent'` (default `'api'`) |
| **Returns** | `ChangeSet` applied to the editor (document replaced) |
```javascript
editor.importContent('# Hello\n\n**bold**', 'markdown');
editor.importContent('Hello world
', 'html');
```
### `exportContent(input)`
| | |
|---|---|
| **Parameters** | `input` — format string (`'html'`, `'markdown'`, `'mdx'`, `'json'`) **or** `{ format, index?, length? }` for a slice |
| **Returns** | `string` — serialized content |
| **Throws** | `SerializationError` when export is unsupported (e.g. native editor table → Markdown/MDX) |
```javascript
const md = editor.exportContent('markdown');
const html = editor.exportContent('html');
const slice = editor.exportContent({ format: 'html', index: 0, length: 50 });
```
Aliases: `editor.import()` / `editor.export()` — same behavior.
### `listExportFormats()`
| | |
|---|---|
| **Returns** | `string[]` — formats registered for this editor (default includes `html`, `markdown`, `mdx`, `json`) |
```javascript
editor.listExportFormats(); // ['html', 'markdown', 'mdx', 'json', …]
```
### `getExportWarnings(input)`
| | |
|---|---|
| **Parameters** | Same shape as `exportContent` — format string or `{ format, index?, length? }`. Only **`markdown`** and **`mdx`** produce warnings. |
| **Returns** | `SafetyIssue[]` — `{ feature, safety: 'lossy' \| 'unsupported', message }`. Does **not** throw. |
```javascript
const warnings = editor.getExportWarnings('markdown');
if (warnings.some((w) => w.safety === 'unsupported')) {
// e.g. native editor table — use exportContent('html') instead
}
const md = editor.exportContent('markdown'); // throws if unsupported content remains
```
## Content
| Method | Description |
|--------|-------------|
| `getContents(index?, length?)` | ChangeSet JSON |
| `setContents(delta, source?)` | Replace document |
| `getText(index?, length?)` | Plain text |
| `getLength()` | Document length |
| `getSemanticHTML(index?, length?)` | HTML string |
| `insertText(index, text, formats?, source?)` | Insert text |
| `insertEmbed(index, name, value, source?)` | Insert image, video, formula |
| `deleteText(index, length, source?)` | Delete range |
| `updateContents(delta, source?)` | Apply ChangeSet |
## Formatting
| Method | Description |
|--------|-------------|
| `format(name, value, source?)` | Format at cursor |
| `formatText(index, length, name, value, source?)` | Format range |
| `formatLine(index, length, name, value, source?)` | Block format |
| `getFormat(index?, length?)` | Active formats |
| `removeFormat(index, length, source?)` | Clear inline formats |
## Selection
| Method | Description |
|--------|-------------|
| `getSelection(focus?)` | `{ index, length }` or null |
| `setSelection(index, length?, source?)` | Set selection |
| `getBounds(index, length?)` | Pixel rect for caret/range |
| `focus()` / `blur()` / `hasFocus()` | Focus management |
## History
Access via `editor.history` (always loaded). Configure with `modules.history`.
| Method | Description |
|--------|-------------|
| `history.undo()` | Undo |
| `history.redo()` | Redo |
| `history.clear()` | Clear stacks |
| `history.cutoff()` | Start a new undo branch |
Keyboard: `Ctrl+Z` / `Ctrl+Shift+Z` when the editor is focused.
**Module options** (`modules.history`):
| Option | Default | Description |
|--------|---------|-------------|
| `delay` | `1000` | Milliseconds before merging consecutive undo steps |
| `maxStack` | `100` | Maximum undo depth |
| `userOnly` | `false` | When `true`, only `'user'`-sourced changes are undoable |
## Modules
Core modules (`clipboard`, `keyboard`, `history`, `uploader`) load automatically. Opt-in modules require `modules.` in constructor options.
```javascript
editor.getModule('toolbar');
editor.getModule('table');
editor.getModule('syntax');
editor.getModule('imageResize');
editor.clipboard; // shorthand
editor.keyboard;
editor.uploader;
editor.history;
```
### Toolbar (`toolbar`)
| | |
|---|---|
| **Enable** | `modules: { toolbar: […] }` — button groups array, `'#selector'`, or `{ container, handlers }` |
| **Access** | `editor.getModule('toolbar')` |
Pass an array to auto-create `div.lxr-toolbar` as the **first child** of the mount element:
```javascript
modules: {
toolbar: [
['bold', 'italic', 'underline'],
[{ header: [1, 2, false] }],
['link', 'image', 'video', 'formula', 'table'],
['clean'],
],
}
```
Custom handlers override theme defaults:
```javascript
modules: {
toolbar: {
container: [['image'], ['table']],
handlers: {
table() {
this.lextrix.getModule('table')?.insertTable(3, 3);
},
},
},
}
```
More: [Configuration — Toolbar buttons](/docs/configuration#toolbar-buttons)
### Table (`table`)
| | |
|---|---|
| **Enable** | `modules: { table: true }` |
| **Access** | `editor.getModule('table')` |
```javascript
editor.getModule('table')?.insertTable(3, 4); // rows, columns
```
Also: `insertRowAbove()`, `insertRowBelow()`, `insertColumnLeft()`, `insertColumnRight()`, `deleteRow()`, `deleteColumn()`.
Native editor tables **cannot** export to Markdown/MDX — use `exportContent('html')`. See [Serialization](/docs/serialization).
### Syntax highlighting (`syntax`)
| | |
|---|---|
| **Enable** | `modules: { syntax: true }` or options object |
| **Requires** | [highlight.js](https://highlightjs.org/) on the page (`window.hljs`) |
```javascript
modules: {
syntax: {
hljs: window.hljs,
languages: [
{ key: 'javascript', label: 'JavaScript' },
{ key: 'python', label: 'Python' },
],
},
}
```
Without highlight.js the module no-ops (code blocks still work, without token colors).
### Uploader (`uploader`)
Always loaded. Handles drag-and-drop and the toolbar **image** button.
| Option | Default | Description |
|--------|---------|-------------|
| `mimetypes` | `['image/png', 'image/jpeg']` | Allowed file types |
| `handler` | base64 embed | `(range, files) => void` — `this.lextrix` is the editor |
```javascript
modules: {
uploader: {
mimetypes: ['image/png', 'image/jpeg', 'image/webp'],
async handler(range, files) {
for (const file of files) {
const body = new FormData();
body.append('file', file);
const res = await fetch('/api/upload', { method: 'POST', body });
const { url } = await res.json();
this.lextrix.insertEmbed(range.index, 'image', url, 'user');
range.index += 1;
}
},
},
}
```
### Clipboard (`clipboard`)
Always loaded. Normalizes paste from HTML, Word, Google Docs.
| Option | Description |
|--------|-------------|
| `matchers` | `[selector, matcherFn][]` — custom HTML → ChangeSet rules |
```javascript
modules: {
clipboard: {
matchers: [
['B', (node, delta) => delta.insert(node.textContent ?? '', { bold: true })],
],
},
}
```
### Image resize (`imageResize`)
Opt-in module. When exactly **one image embed** is selected, shows a corner drag handle and preserves aspect ratio while resizing.
| | |
|---|---|
| **Enable** | `modules: { imageResize: true }` or an options object (see below) |
| **Access** | `editor.getModule('imageResize')` |
| **Applies to** | Image embeds only — not video, formula, or other embeds |
**Options** (pass as `modules.imageResize`):
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `minWidth` | `number` | `48` | Minimum width in pixels |
| `maxWidth` | `number` \| `null` | `null` | Maximum width; `null` = editor root width |
```javascript
modules: {
toolbar: [['bold', 'italic'], ['link', 'image'], ['clean']],
imageResize: { minWidth: 48, maxWidth: null },
},
```
Select an image in the editor, then drag the handle. Resize commits `width` and `height` attributes on the image blot.
**Programmatic resize** (same attributes the handle sets):
```javascript
editor.formatText(index, 1, { width: '400', height: '300' }, 'user');
```
Requires theme CSS (included in `lextrix/snow.css`, etc.) for `.lxr-image-resize` overlay styles.
The overlay mounts on `.lxr-container` and repositions after layout settles (selection, scroll, and resize). Always call `editor.destroy()` on unmount.
More: [Configuration — Image resize](/docs/configuration#image-resize) · [Modules](/docs/plugins#image-resize)
## Registration helpers
Call **before** `new Lextrix()` unless noted.
### `registerSerializer(serializer)`
Register a custom import/export format globally.
| | |
|---|---|
| **Parameter** | `{ format, import(content, context?), export(changeSet, context?) }` |
| **See also** | [Serialization — Custom serializers](/docs/serialization) |
```javascript
import { registerSerializer } from 'lextrix';
registerSerializer({
format: 'plain',
import: (text) => [{ insert: text }],
export: (delta) => delta.getText(),
});
```
### `unregisterSerializer(format)`
Remove a registered format. Returns `boolean`.
### `registerMdxComponent(name, handler)`
Register a JSX component handler for MDX import/export. Experimental — see [Serialization](/docs/serialization).
### `getMarkdownExportWarnings(delta)`
Headless helper — same warnings as `editor.getExportWarnings('markdown')` without an editor instance.
```javascript
import { getMarkdownExportWarnings, ChangeSet } from 'lextrix';
getMarkdownExportWarnings(new ChangeSet().insert('Hello\n'));
```
## Events
```javascript
editor.on('text-change', (delta, oldDelta, source) => {});
editor.on('selection-change', (range, oldRange, source) => {});
editor.on('editor-change', (eventName, ...args) => {});
```
Sources: `'user'`, `'api'`, `'silent'`.
## ChangeSet
```javascript
import { ChangeSet } from 'lextrix';
new ChangeSet()
.retain(5)
.insert('Hello', { bold: true })
.delete(2);
```
Legacy name `Delta` is not supported — use `ChangeSet`. See the [ChangeSet guide](https://github.com/rishureetesh/lextrix/blob/main/docs/guides/change-set.md).
## Registry paths
Register custom formats or modules:
```javascript
import Lextrix, { lxrPath } from 'lextrix';
Lextrix.register({ [lxrPath.format('callout')]: CalloutBlot });
Lextrix.register({ [lxrPath.module('mentions')]: MentionsModule });
```
Bare paths like `formats/bold` or `parchment` throw — use `lxr/*` paths only. See the [Formats guide](/docs/plugins#registration). Advanced blot helpers (`defineInlineTagFormat`, …) require the monorepo — see [Framework integration](/docs/frameworks).
---
## Themes
Source: https://iamreetesh.com/docs/themes
Four built-in themes: **snow**, **bubble**, **slate**, **dawn**.
## Install CSS
Themes are separate CSS files. Import exactly one (or swap at runtime):
```javascript
import 'lextrix/snow.css';
// import 'lextrix/bubble.css';
// import 'lextrix/slate.css';
// import 'lextrix/dawn.css';
```
| Theme | Toolbar style | Best for |
|-------|---------------|----------|
| `snow` | Fixed bar above editor | Docs, CMS, classic UI |
| `bubble` | Floating on selection | Comments, inline editing |
| `slate` | Dark snow-like bar | Dark apps |
| `dawn` | Warm palette | Marketing / editorial |
## Basic usage
```javascript
import Lextrix from 'lextrix';
import 'lextrix/snow.css';
const mount = document.createElement('div');
document.getElementById('wrapper').appendChild(mount);
new Lextrix(mount, {
theme: 'snow',
modules: {
toolbar: [
['bold', 'italic', 'underline'],
[{ header: [1, 2, false] }],
['link', 'clean'],
],
},
});
```
Bubble uses a smaller default toolbar set — see [Configuration](/docs/configuration#toolbar-buttons).
## Switch theme at runtime
1. Save document: `const contents = editor.getContents()`
2. Load new CSS (replace `` href or dynamic `import()`)
3. **Clear wrapper** (not just the mount div) — see [DOM mounting](/docs/dom-mounting)
4. Create fresh inner mount + `new Lextrix(mount, { theme: newTheme, … })`
5. `editor.setContents(contents)`
```javascript
async function switchTheme(wrapper, editor, newTheme) {
const contents = editor.getContents();
await loadThemeCss(newTheme); // your helper — swap link href
wrapper.replaceChildren();
const mount = document.createElement('div');
wrapper.appendChild(mount);
return new Lextrix(mount, {
theme: newTheme,
modules: { toolbar: toolbarFor(newTheme) },
}).setContents(contents);
}
```
### Race conditions
If the user clicks themes quickly:
- **Debounce** or disable buttons while switching
- **Await** CSS load before creating the editor
- Always **`wrapper.replaceChildren()`** before each `new Lextrix` — never stack instances
Duplicate toolbars = cleanup skipped on parent wrapper. Not a Lextrix race bug; a DOM lifecycle bug.
## React example
Prefer [`@lextrix/react`](/docs/react) — remount with `key` when the theme changes:
```tsx
import { LextrixEditor } from '@lextrix/react';
import 'lextrix/snow.css'; // or dynamically swap theme CSS in your app
export function ThemedEditor({ theme }: { theme: 'snow' | 'bubble' | 'slate' | 'dawn' }) {
return (
);
}
```
Load the matching `lextrix/{theme}.css` when switching themes (global `` or bundler import).
Manual mount pattern (without `@lextrix/react`): [Frameworks](/docs/frameworks) · [DOM mounting](/docs/dom-mounting).
---
## Configuration
Source: https://iamreetesh.com/docs/configuration
# Configuration
> **Integrating in React or switching themes?** Call `editor.destroy()` on teardown — see [DOM mounting](/docs/dom-mounting).
## Editor options
```typescript
new Lextrix(container, {
theme: 'snow', // snow | bubble | slate | dawn
placeholder: '',
readOnly: false,
bounds: null, // tooltip positioning root
debug: false,
formats: null, // null = all registered formats; or string[]
modules: { /* … */ },
});
```
## Modules
### Always enabled
These load automatically — configure via `modules.`:
| Module | Options | Purpose |
|--------|---------|---------|
| `clipboard` | `matchers` | Paste HTML → ChangeSet |
| `keyboard` | bindings | Shortcuts |
| `history` | `delay`, `maxStack`, `userOnly` | Undo / redo |
| `uploader` | `mimetypes`, `handler` | Drag-drop & toolbar image upload |
### Opt-in modules
| Module | Enable | Notes |
|--------|--------|-------|
| `toolbar` | `toolbar: […]` or `'#selector'` | Button names — see below |
| `syntax` | `syntax: true` | Needs highlight.js |
| `table` | `table: true` | Table blots + cell keyboard nav |
| `imageResize` | `imageResize: true` | Drag handle on selected images |
### Toolbar buttons
Pass an array of groups:
```javascript
modules: {
toolbar: [
['bold', 'italic', 'underline'],
[{ header: [1, 2, 3, 4, 5, false] }],
[{ list: 'ordered' }, { list: 'bullet' }, { list: 'check' }],
[{ indent: '-1' }, { indent: '+1' }],
[{ align: [] }, { direction: 'rtl' }],
['blockquote', 'code-block'],
['link', 'image', 'video', 'formula'],
['clean'],
],
}
```
Or point to existing DOM:
```javascript
modules: { toolbar: '#my-toolbar' }
```
When `toolbar` is an **array**, Lextrix creates `div.lxr-toolbar` as the **first child** of your mount element. Call `editor.destroy()` when tearing down — see [DOM mounting](/docs/dom-mounting).
Custom handlers override theme defaults:
```javascript
modules: {
toolbar: {
container: ['image'],
handlers: {
image() { /* custom */ },
},
},
}
```
## Image upload to your API
Default uploader embeds images as base64. Override the handler:
```javascript
modules: {
uploader: {
mimetypes: ['image/png', 'image/jpeg', 'image/webp'],
async handler(range, files) {
for (const file of files) {
const body = new FormData();
body.append('file', file);
const res = await fetch('/api/upload', { method: 'POST', body });
const { url } = await res.json();
this.lextrix.insertEmbed(range.index, 'image', url, 'user');
range.index += 1;
}
},
},
}
```
The theme **image** button and drag-and-drop both call `uploader.upload()`.
## Image resize
Enable the module and select an image in the editor — a corner handle appears.
```javascript
modules: {
imageResize: {
minWidth: 48,
maxWidth: null, // null = editor width
},
}
```
Resize sets `width` and `height` attributes on the `
` blot.
Programmatic resize:
```javascript
editor.formatText(index, 1, { width: '400', height: '300' }, 'user');
```
See also: [API reference — imageResize](/docs/commands#image-resize-imageresize) · [Modules — Image resize](/docs/plugins#image-resize)
## Format whitelist
Restrict allowed formats:
```javascript
formats: ['bold', 'italic', 'link', 'header', 'list', 'image'],
```
Registered format names: `bold`, `italic`, `underline`, `strike`, `link`, `script`, `header`, `list`, `blockquote`, `code`, `code-block`, `color`, `background`, `font`, `size`, `align`, `direction`, `indent`, `image`, `video`, `formula`.
## Syntax highlighting
Load highlight.js, then:
```javascript
modules: {
syntax: {
hljs: window.hljs,
languages: [
{ key: 'javascript', label: 'JavaScript' },
{ key: 'python', label: 'Python' },
],
interval: 1000,
},
}
```
## Tables
```javascript
modules: { table: true }
// Insert programmatically
editor.getModule('table').insertTable(3, 4); // rows, columns
```
## Modular registration (advanced)
Build a custom bundle from workspace packages:
```javascript
import Lextrix, { registerBlots } from 'lextrix-core';
import { registerFormats } from 'lextrix-formats';
import { registerCoreModules, registerOptionalModules } from 'lextrix-modules';
import { registerUI } from 'lextrix-ui';
import { registerThemes } from 'lextrix-themes';
registerBlots(Lextrix);
registerFormats(Lextrix);
registerCoreModules(Lextrix);
registerOptionalModules(Lextrix);
registerUI(Lextrix);
registerThemes(Lextrix);
```
See [Modules](/docs/plugins) for per-module details and authoring.
---
## Plugin System
Source: https://iamreetesh.com/docs/plugins
# Modules
## Core (always loaded)
### Clipboard
Normalizes paste from HTML, Word, Google Docs, etc. Add custom matchers:
```javascript
modules: {
clipboard: {
matchers: [
['B', (node, delta) => delta.insert(node.textContent, { bold: true })],
],
},
}
```
### Keyboard
Default bindings for Enter in lists/headers, tab indent, link shortcuts (snow theme adds Ctrl/Cmd+K). Extend:
```javascript
editor.keyboard.addBinding({ key: 'b', shortKey: true }, (range, context) => {
editor.format('bold', !context.format.bold);
});
```
### History
| Option | Default | Description |
|--------|---------|-------------|
| `delay` | `1000` | ms before merging undo steps |
| `maxStack` | `100` | Max undo depth |
| `userOnly` | `false` | Ignore API changes |
### Uploader
Handles drag-and-drop and cooperates with the toolbar image button. See [Configuration](/docs/configuration#image-upload-to-your-api).
---
## Optional
### Toolbar
Renders formatting controls. Theme provides default handlers for `image`, `video`, and `formula`.
Snow theme auto-builds a default toolbar when `modules.toolbar` is set without a `container`.
### Syntax
Requires **highlight.js**. Adds a language `