# Quill Rich Text Editor — User Guide

Quill is a free, open source **WYSIWYG rich text editor** for the modern web. You give it a container element, and it turns into a powerful editor with formatting, themes, and a clean programmatic API. Quill is built around a structured document model (the **Delta** format) instead of raw HTML, which makes its content predictable to read, write, and transform across clients and platforms.

This guide is written for the web developer who wants to **embed Quill in a page or application**. It covers installation, configuration, everyday content and formatting tasks, the built‑in modules and themes, troubleshooting, and a complete API quick reference — all derived from the Quill 2.0.3 source.

> [!NOTE]
> Quill is distributed as a browser library (`quill` on npm). It has no command‑line interface and no server component — everything runs in the browser. If you are looking for deployment, build, and architecture details, see the companion **Admin Guide**.

---

## 1. Quick Start

The fastest way to get an editor on the page is the CDN full build with the `snow` theme.

```html
<!-- Include the snow theme stylesheet -->
<link href="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.snow.css" rel="stylesheet" />

<!-- Create the editor container with some initial content -->
<div id="editor">
  <p>Hello World!</p>
  <p>Some initial <strong>bold</strong> text</p>
  <p><br /></p>
</div>

<!-- Include the Quill library -->
<script src="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.js"></script>

<!-- Initialize the editor -->
<script>
  const quill = new Quill('#editor', {
    theme: 'snow'
  });
</script>
```

When the page loads, the contents of `#editor` become the editor's initial document, and a formatting toolbar appears above it.

```text
A bordered editor with a toolbar (headers, bold, italic, underline, link,
lists, clean) above an editable area pre-filled with "Hello World!".
```

> [!TIP]
> The element you point Quill at is **replaced** by the editor — its existing inner HTML becomes the starting document. Keep a separate element for anything you do not want consumed.

---

## 2. Installation

Quill ships in several forms. Pick the one that matches how your project loads JavaScript.

### 2.1 Choosing a build

Quill offers two builds:

| Build | Entry | What it includes |
|-------|-------|------------------|
| **Full** | `quill` (npm) / `quill.js` (CDN) | Core library plus common themes (`snow`, `bubble`), all standard formats, and default modules. The easiest way to start. |
| **Core** | `quill/core` (npm) / `quill.core.js` (CDN) | Only the core library. You register the formats, modules, and themes you need yourself. Smallest footprint. |

> [!IMPORTANT]
> Importing `"quill"` anywhere in your project registers the **full** build's formats and modules. If you want the slim core build, import `"quill/core"` consistently and never import `"quill"` directly.

### 2.2 CDN

A globally distributed CDN is available, backed by jsDelivr. This requires no build step or package manager.

=== "Full build (snow theme)"

    ```html
    <script src="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.js"></script>
    <link href="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.snow.css" rel="stylesheet">

    <div id="editor">
      <h2>Demo Content</h2>
      <p>Preset build with the <code>snow</code> theme.</p>
    </div>

    <script>
      const quill = new Quill('#editor', { theme: 'snow' });
    </script>
    ```

=== "Core build (no theme)"

    ```html
    <link href="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.core.css" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.core.js"></script>

    <div id="editor">
      <p>Core build with no theme or non-essential modules.</p>
    </div>

    <script>
      const quill = new Quill('#editor');
    </script>
    ```

CDN builds expose `Quill` on the global `window` object. From there, `Quill.import()` gives you access to internal components (formats, modules, themes, the `Delta` class).

### 2.3 npm

If your project uses a bundler such as Webpack or Vite, install from npm:

```bash
npm install quill@2.0.3
```

```text
added 1 package, and audited N packages in Xs
```

Import the full build, or the core build:

```js
import Quill from 'quill';
// Or, for the slim core build:
// import Quill from 'quill/core';

const quill = new Quill('#editor');
```

In a bundler environment you can import individual formats and modules directly, which is more natural than `Quill.import()`:

```js
import Quill from 'quill';
import { Delta } from 'quill';        // the Delta document type
import Link from 'quill/formats/link'; // a single format
// Equivalent: const Delta = Quill.import('delta');
//             const Link  = Quill.import('formats/link');
```

### 2.4 Styles

The npm package bundles the same stylesheets as the CDN, in the `dist` directory. Import the one matching your theme:

```js
import 'quill/dist/quill.snow.css';   // snow theme
// import 'quill/dist/quill.bubble.css'; // bubble theme
// import 'quill/dist/quill.core.css';   // core build, no theme styling
```

| Stylesheet | Use with |
|------------|----------|
| `quill.core.css` | Core build / no theme. Provides only essential editor styling. |
| `quill.snow.css` | `snow` theme (toolbar above the editor). Includes core styling. |
| `quill.bubble.css` | `bubble` theme (inline tooltip toolbar). Includes core styling. |

> [!WARNING]
> The theme stylesheet is **not** loaded automatically when you set `theme: 'snow'`. You must include the matching CSS yourself, or the editor will render unstyled.

### 2.5 Requirements

Quill runs in any modern browser environment that provides the standard DOM APIs it relies on (`DOMParser`, `MutationObserver`, `Selection`/`Range`, `beforeinput` events). The npm package targets the `defaults` browserslist. There are no other runtime dependencies you need to install — `parchment`, `quill-delta`, `eventemitter3`, and `lodash-es` are bundled.

---

## 3. Your First Editor in Depth

```js
const quill = new Quill('#editor', {
  theme: 'snow',
  placeholder: 'Compose an epic...',
  readOnly: false,
});
```

The `Quill` constructor takes two arguments:

1. **A container** — a CSS selector string (the first match is used) or a DOM element. If the container is not empty, its current contents become the initial document.
2. **An options object** (optional) — see [Configuration](#4-configuration).

```js
// Selector string
const quill = new Quill('#editor');

// DOM element
const container = document.getElementById('editor');
const quill = new Quill(container);
```

> [!CAUTION]
> If the selector matches nothing, or the element is otherwise invalid, Quill throws `Invalid Quill container`. Make sure the element exists in the DOM before you construct the editor.

---

## 4. Configuration

Pass an options object as the second argument to customize the editor. Every key is optional.

```js
const quill = new Quill('#editor', {
  debug: 'info',
  modules: {
    toolbar: true,
  },
  placeholder: 'Compose an epic...',
  theme: 'snow',
});
```

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `theme` | `string` | minimal default | Name of the theme: `"snow"` or `"bubble"`. An invalid or falsy value loads a minimal default theme (no toolbar styling). The theme's stylesheet must still be included manually. |
| `modules` | `object` | see [Modules](#8-modules) | Configuration for built‑in and custom modules (toolbar, keyboard, history, etc.). |
| `placeholder` | `string` | `""` | Text shown when the editor is empty. |
| `readOnly` | `boolean` | `false` | If `true`, the editor starts in read‑only mode (no user edits). |
| `bounds` | `HTMLElement \| string \| null` | `document.body` | Element (or selector) within which UI elements like tooltips are confined. Only left/right boundaries are considered. |
| `formats` | `string[] \| null` | `null` | Allowed format names. `null` means all registered formats are allowed. Pass an array to restrict formatting. Ignored if `registry` is set. |
| `registry` | `Parchment.Registry` | shared global | A custom Parchment registry for advanced format/blot customization. Specifying this ignores the `formats` option. |
| `debug` | `DebugLevel \| boolean` | `'warn'` | Shortcut for `Quill.debug()`. Sets logging verbosity. **Static** — affects all editors on the page. |

### 4.1 Restricting formats

To allow only a subset of formats, pass their names:

```js
const quill = new Quill('#editor', {
  formats: ['italic'], // only italic is permitted in the contents
});

const Delta = Quill.import('delta');
quill.setContents(
  new Delta()
    .insert('Only ')
    .insert('italic', { italic: true })
    .insert(' is allowed. ')
    .insert('Bold', { bold: true }) // bold is stripped
    .insert(' is not.')
);
```

> [!NOTE]
> Restricting formats affects what can exist in the document, including content pasted from outside. Disallowed formats are removed rather than rejected.

### 4.2 Read‑only editors

```js
const quill = new Quill('#editor', {
  readOnly: true,
  modules: { toolbar: null }, // hide the toolbar entirely
  theme: 'snow',
});
```

You can toggle the mode at runtime with `quill.enable()` / `quill.disable()`.

---

## 5. Understanding Content: Delta vs. HTML

Quill represents documents internally as a **Delta** — a JSON structure describing content as a sequence of insert operations with optional formatting attributes. Deltas are the canonical, lossless representation of editor content; HTML is treated as an import/export format.

```js
const Delta = Quill.import('delta');

const contents = new Delta()
  .insert('Gandalf', { bold: true })
  .insert(' the ')
  .insert('Grey', { color: '#cccccc' })
  .insert('\n');
```

This Delta describes the text "Gandalf the Grey" with "Gandalf" bold and "Grey" colored grey, followed by a newline (which ends the line/paragraph).

> [!TIP]
> Prefer Deltas over HTML whenever you can. They are unambiguous, diff‑able, and safe to store. Reach for HTML only at the boundaries of your app (e.g. importing pasted content or rendering a static export).

Key things to know:

- **Newlines (`\n`) delimit lines**, and line‑level formats (header, list, alignment) attach to the newline character of the line they format.
- `getContents()` returns a Delta; `setContents()` accepts one.
- `updateContents()` applies a *change* Delta (with `retain`/`delete`/`insert` ops) on top of the current document.

---

## 6. Everyday Usage

All of the methods below are instance methods on your `quill` object. Most content‑modifying methods accept either an `(index, length)` pair or a selection `Range`, and an optional **source** (`'api'`, `'user'`, or `'silent'`) that controls event emission.

### 6.1 Inserting and deleting text

```js
// Insert plain text at index 0
quill.insertText(0, 'Hello');

// Insert formatted text
quill.insertText(5, ' World', 'bold', true);
quill.insertText(11, '!', { italic: true, color: 'red' });

// Delete 6 characters starting at index 5
quill.deleteText(5, 6);
```

### 6.2 Replacing all content

```js
const Delta = Quill.import('delta');

// Replace the whole document with a Delta
quill.setContents(new Delta().insert('A fresh start\n'));

// Replace the whole document with plain text
quill.setText('Just plain text\n');
```

### 6.3 Applying a change on top of existing content

```js
const Delta = Quill.import('delta');

// Keep the first 6 characters, then insert bold "World"
quill.updateContents(
  new Delta()
    .retain(6)
    .insert('World', { bold: true })
);
```

### 6.4 Reading content out

```js
quill.getText();           // plain text of the whole document
quill.getText(0, 5);       // plain text of a range
quill.getContents();       // Delta of the whole document
quill.getContents(0, 5);   // Delta of a range
quill.getLength();         // length of the document (includes the trailing \n)
quill.getSemanticHTML();   // clean, semantic HTML export of the contents
```

> [!NOTE]
> `getSemanticHTML()` produces clean HTML derived from the Delta, suitable for display or storage — it is not the editor's raw internal DOM. See the [Troubleshooting](#11-troubleshooting) section for known edge cases with lists.

### 6.5 Formatting

```js
// Format the current selection (inline or line format, auto-detected)
quill.format('bold', true);
quill.format('align', 'center'); // line-level format

// Format a specific range of text (inline)
quill.formatText(0, 5, 'bold', true);
quill.formatText(0, 5, { italic: true, underline: true });

// Format whole lines touched by a range (block/line formats)
quill.formatLine(0, 10, 'header', 2);
quill.formatLine(0, 10, 'list', 'bullet');

// Remove all formatting from a range
quill.removeFormat(0, 12);

// Inspect formatting
quill.getFormat();        // formats of current selection
quill.getFormat(0, 5);    // formats common to a range
```

> [!TIP]
> Use `formatText` for **inline** formats (bold, italic, color, link) and `formatLine` for **line/block** formats (header, list, blockquote, align, indent, code‑block). `format()` figures out which kind you mean based on the registered format's scope.

### 6.6 Embeds (images, videos, formulas)

```js
// Insert an image embed at index 10
quill.insertEmbed(10, 'image', 'https://example.com/cat.png');

// Insert a video embed
quill.insertEmbed(0, 'video', 'https://www.youtube.com/embed/QHH3iSeDBLo');
```

### 6.7 Selection and focus

```js
quill.setSelection(0, 5);          // select characters 0–5
quill.setSelection(10);            // collapsed cursor at index 10
quill.getSelection();              // current Range, or null if not focused
quill.getSelection(true);          // focus first, then return the Range
quill.focus();                     // focus the editor
quill.blur();                      // remove focus
quill.hasFocus();                  // boolean
quill.getBounds(0, 5);             // pixel bounds of a range (for positioning UI)
```

A **Range** is simply `{ index, length }`. A collapsed cursor has `length === 0`.

### 6.8 Enabling and disabling

```js
quill.disable();        // read-only
quill.enable();         // editable again
quill.enable(false);    // same as disable()
quill.isEnabled();      // boolean
```

---

## 7. Listening for Changes (Events)

Register handlers with `quill.on(eventName, handler)`. Quill emits three events, all exposed as constants on `Quill.events`.

| Event constant | String | Fired when | Handler signature |
|----------------|--------|-----------|-------------------|
| `Quill.events.TEXT_CHANGE` | `'text-change'` | The document contents change | `(delta, oldContents, source)` |
| `Quill.events.SELECTION_CHANGE` | `'selection-change'` | The cursor or selection moves | `(range, oldRange, source)` |
| `Quill.events.EDITOR_CHANGE` | `'editor-change'` | Either of the above (fires first, including for `'silent'` text changes) | `(eventName, ...args)` |

```js
quill.on('text-change', (delta, oldDelta, source) => {
  if (source === 'user') {
    console.log('A user edited the document:', delta);
  }
});

quill.on('selection-change', (range, oldRange, source) => {
  if (range === null) {
    console.log('Editor lost focus');
  } else {
    console.log('Selection is now', range.index, range.length);
  }
});
```

Manage handlers with:

```js
quill.once('text-change', handler); // fire at most once
quill.off('text-change', handler);  // remove a handler
```

### 7.1 Event sources

Every change carries a **source**:

| Source | Constant | Meaning |
|--------|----------|---------|
| `'user'` | `Quill.sources.USER` | The change originated from direct user interaction (typing, toolbar click, paste). |
| `'api'` | `Quill.sources.API` | The change came from your code calling an API method. This is the default for most methods. |
| `'silent'` | `Quill.sources.SILENT` | The change should not emit `text-change` / `selection-change` (it still emits `editor-change`). |

> [!TIP]
> Filter on `source === 'user'` when you want to react only to human edits — for example, to mark a document "dirty" or to sync changes to a server. API‑driven changes you made yourself usually shouldn't trigger that logic.

---

## 8. Modules

Modules add functionality. Configure them under the `modules` option. Several are enabled by default; others you opt into.

```js
const quill = new Quill('#editor', {
  modules: {
    toolbar: '#toolbar',
    history: { delay: 2000, maxStack: 500, userOnly: true },
    syntax: true,
  },
  theme: 'snow',
});
```

Set a module to `true` to enable it with defaults, to an options object to customize, or to `false`/`null` to disable it.

| Module | Default state | Purpose |
|--------|---------------|---------|
| `clipboard` | on | Handles copy / cut / paste, including HTML normalization from Word and Google Docs. |
| `keyboard` | on | Keyboard shortcuts and custom key bindings. |
| `history` | on | Undo / redo stack. |
| `uploader` | on | Drag‑and‑drop / paste image upload. |
| `toolbar` | added by theme | The formatting toolbar. |
| `syntax` | off | Syntax‑highlighted code blocks (requires highlight.js). |
| `table` | off | Editable tables. |

### 8.1 Toolbar

The toolbar is the most commonly configured module. You can provide a **container selector**, an **array configuration**, or an existing DOM element.

```js
// 1. Simple array config — Quill builds the toolbar HTML for you
const quill = new Quill('#editor', {
  modules: {
    toolbar: [
      [{ header: [1, 2, 3, false] }],
      ['bold', 'italic', 'underline', 'strike'],
      [{ color: [] }, { background: [] }],
      [{ list: 'ordered' }, { list: 'bullet' }, { list: 'check' }],
      [{ align: [] }],
      ['link', 'image', 'video'],
      ['clean'],
    ],
  },
  theme: 'snow',
});
```

```js
// 2. Point at your own toolbar HTML
const quill = new Quill('#editor', {
  modules: { toolbar: '#my-toolbar' },
  theme: 'snow',
});
```

```html
<div id="my-toolbar">
  <button class="ql-bold"></button>
  <button class="ql-italic"></button>
  <select class="ql-header">
    <option value="1"></option>
    <option value="2"></option>
    <option selected></option>
  </select>
  <button class="ql-clean"></button>
</div>
```

Built‑in toolbar controls map to formats by the `ql-{format}` class. The toolbar ships with handlers for the special controls below; the rest simply toggle their named format.

| Control | Behavior |
|---------|----------|
| `clean` | Removes inline formatting from the selection (or all inline formats at the cursor). |
| `link` | Prompts for a URL (in the bare toolbar) and applies the `link` format. |
| `list` | Applies `ordered`, `bullet`, or toggles a `checked`/`unchecked` checklist (`value: 'check'`). |
| `indent` | `'+1'` / `'-1'` increases or decreases indentation (direction‑aware). |
| `direction` | Sets text direction (`'rtl'`) and coordinates alignment. |

> [!NOTE]
> The `snow` and `bubble` themes provide a default toolbar if you enable the toolbar without specifying a container. `snow`'s default is headers + bold/italic/underline/link + ordered/bullet lists + clean. `bubble`'s default is bold/italic/link + header 1/header 2/blockquote.

#### Custom toolbar handlers

Override what a toolbar button does:

```js
const quill = new Quill('#editor', {
  modules: {
    toolbar: {
      container: '#toolbar',
      handlers: {
        bold(value) {
          // `this` is the toolbar module; `value` is the new active state
          this.quill.format('bold', value);
        },
      },
    },
  },
});
```

### 8.2 Keyboard

The keyboard module powers shortcuts and lets you add custom key bindings. Add a binding with `quill.keyboard.addBinding`:

```js
quill.keyboard.addBinding(
  { key: 'b', shortKey: true }, // shortKey = Cmd on macOS, Ctrl elsewhere
  (range, context) => {
    quill.format('bold', !context.format.bold);
  }
);

// With a context condition (only when the selection is collapsed at offset 0)
quill.keyboard.addBinding(
  { key: 'Backspace' },
  { collapsed: true, offset: 0 },
  (range, context) => {
    // custom backspace behavior
  }
);
```

Binding keys include `key` (a key name, character, or key code), plus optional modifiers: `shortKey`, `shiftKey`, `altKey`, `metaKey`, `ctrlKey`. A handler returning `true` lets the default behavior proceed; returning `false` (or nothing meaningful) prevents it.

> [!NOTE]
> Quill already binds `Enter`, `Backspace`/`Delete` (with special list/indent handling), and — via the history module — undo (`Cmd/Ctrl+Z`), redo (`Cmd/Ctrl+Shift+Z`, plus `Ctrl+Y` on Windows).

### 8.3 History (undo / redo)

```js
const quill = new Quill('#editor', {
  modules: {
    history: {
      delay: 1000,    // ms window in which consecutive changes are merged
      maxStack: 100,  // maximum number of undo entries kept
      userOnly: false // if true, only 'user'-source changes are undoable
    },
  },
});

quill.history.undo();
quill.history.redo();
quill.history.clear();
```

| Option | Default | Description |
|--------|---------|-------------|
| `delay` | `1000` | Changes within this many milliseconds are merged into a single undo step. |
| `maxStack` | `100` | Maximum undo/redo entries retained. |
| `userOnly` | `false` | When `true`, only user edits are recorded; API changes are transformed but not undoable (useful for collaborative editing). |

> [!TIP]
> Set `userOnly: true` in collaborative apps so a user's undo never reverts a change that came from a remote collaborator.

### 8.4 Clipboard

The clipboard module governs paste behavior, normalizing messy HTML (including content pasted from Microsoft Word and Google Docs) into clean Deltas. You can extend it with custom **matchers**:

```js
// Strip formatting from anything pasted by matching all element nodes
const quill = new Quill('#editor', {
  modules: {
    clipboard: {
      matchers: [
        [Node.ELEMENT_NODE, (node, delta) => {
          return delta.compose(new Delta().retain(delta.length(), {
            color: false, background: false,
          }));
        }],
      ],
    },
  },
});

// Programmatic paste of HTML
quill.clipboard.dangerouslyPasteHTML(0, '<b>Bold</b> text');
```

> [!CAUTION]
> `dangerouslyPasteHTML` inserts HTML through the clipboard's normalization but is named "dangerously" for a reason: never pass unsanitized, untrusted HTML to it. See the **Admin Guide → Security** section.

### 8.5 Syntax highlighting

The syntax module highlights code blocks. It requires the third‑party [highlight.js](https://highlightjs.org/) library to be available on the page.

```html
<link href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release/build/styles/atom-one-dark.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release/build/highlight.min.js"></script>
```

```js
const quill = new Quill('#editor', {
  modules: {
    syntax: true, // uses window.hljs by default
    toolbar: [['code-block']],
  },
  theme: 'snow',
});
```

| Option | Default | Description |
|--------|---------|-------------|
| `hljs` | `window.hljs` | The highlight.js instance to use. |
| `interval` | `1000` | How often (ms) highlighting re‑runs after edits. |
| `languages` | Plain, Bash, C++, C#, CSS, Diff, HTML/XML, Java, JavaScript, Markdown, PHP, Python, Ruby, SQL | The language options shown in the code‑block language picker. |

> [!WARNING]
> If you enable `syntax` without highlight.js loaded, Quill throws: `Syntax module requires highlight.js. Please include the library on the page before Quill.`

### 8.6 Tables

The `table` module adds editable tables and an API for manipulating them.

```js
const quill = new Quill('#editor', {
  modules: { table: true },
  theme: 'snow',
});

const table = quill.getModule('table');
table.insertTable(3, 3);   // 3 rows × 3 columns
table.insertRowAbove();
table.insertRowBelow();
table.insertColumnLeft();
table.insertColumnRight();
table.deleteRow();
table.deleteColumn();
table.deleteTable();
```

### 8.7 Image upload (uploader)

The uploader module handles images dropped onto, or pasted into, the editor. By default it accepts `image/png` and `image/jpeg`, reads them as data URLs, and inserts them as image embeds. Provide a custom `handler` to upload to your server instead:

```js
const quill = new Quill('#editor', {
  modules: {
    uploader: {
      mimetypes: ['image/png', 'image/jpeg', 'image/gif'],
      handler(range, files) {
        // upload `files` to your server, then insert the returned URLs
      },
    },
  },
});
```

### 8.8 Accessing a module instance

```js
const toolbar = quill.getModule('toolbar');
const history = quill.getModule('history');
```

---

## 9. Themes

A theme controls the editor's look and its toolbar/tooltip UI. Quill bundles two themes plus a minimal default.

| Theme | `theme` value | Stylesheet | Character |
|-------|---------------|------------|-----------|
| **Snow** | `'snow'` | `quill.snow.css` | A clean, flat toolbar fixed above the editor. The most common choice. |
| **Bubble** | `'bubble'` | `quill.bubble.css` | A tooltip toolbar that appears over the selection. Minimalist, Medium‑style. |
| **Default** | omitted / invalid | `quill.core.css` | No toolbar styling. You build your own UI. |

```js
const quill = new Quill('#editor', { theme: 'bubble' });
```

> [!IMPORTANT]
> Setting `theme` does not load CSS. Always include the matching stylesheet for the theme you choose.

---

## 10. Common Recipes

### 10.1 Save content to your server

```js
quill.on('text-change', (delta, oldDelta, source) => {
  if (source !== 'user') return;
  const contents = quill.getContents();        // a Delta — store this
  fetch('/save', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(contents),
  });
});
```

> [!TIP]
> Store the **Delta**, not HTML. To re‑hydrate, call `quill.setContents(savedDelta)`. To render the document outside Quill (e.g. in an email), generate HTML with `quill.getSemanticHTML()` at export time.

### 10.2 Build a live HTML preview

```js
const preview = document.getElementById('preview');
quill.on('text-change', () => {
  preview.innerHTML = quill.getSemanticHTML();
});
```

### 10.3 Enforce a maximum length

```js
quill.on('text-change', () => {
  const limit = 280;
  if (quill.getLength() > limit) {
    quill.deleteText(limit - 1, quill.getLength());
  }
});
```

### 10.4 Programmatically focus and place the cursor at the end

```js
quill.focus();
quill.setSelection(quill.getLength(), 0);
```

### 10.5 Sync a hidden form field on submit

```js
const editor = document.querySelector('#editor');
const hidden = document.querySelector('input[name="body"]');
document.querySelector('form').addEventListener('submit', () => {
  hidden.value = JSON.stringify(quill.getContents());
});
```

---

## 11. Troubleshooting

### Error: `Invalid Quill container`

**Cause.** The container selector matched no element, or `null`/`undefined` was passed as the container.

**Solution.** Confirm the element exists in the DOM before constructing Quill — initialize after `DOMContentLoaded`, or place the script after the container element.

**Prevention.** Use a specific, unique selector and verify it with `document.querySelector('#editor')` first.

### Error: `Invalid theme {name}. Did you register it?`

**Cause.** You set `theme` to a name that is not registered. Only `'snow'` and `'bubble'` are built in.

**Solution.** Use `'snow'`, `'bubble'`, or omit `theme`. If you wrote a custom theme, register it with `Quill.register('themes/myTheme', MyTheme)` before constructing the editor.

### Error: `Syntax module requires highlight.js. Please include the library on the page before Quill.`

**Cause.** The `syntax` module was enabled but `window.hljs` (highlight.js) is not present.

**Solution.** Load highlight.js before initializing Quill, or pass an explicit instance via `modules.syntax.hljs`.

### The editor renders but has no toolbar styling

**Cause.** The theme stylesheet is missing.

**Solution.** Include `quill.snow.css` (or `quill.bubble.css`) matching your `theme`.

**Prevention.** Treat the theme JS option and its CSS file as a pair — always ship both.

### Pasting from Word or Google Docs loses or mangles list formatting

**Cause.** External HTML (especially from Microsoft Word and Google Docs) is irregular. Quill normalizes it, but some edge cases — particularly bullet vs. numbered lists and empty lines — are imperfect.

**Solution.** Add custom clipboard matchers for the specific patterns you need to preserve. For checklists/ordered lists, verify the result and adjust with `formatLine(..., 'list', ...)` after paste if needed.

**Prevention.** Where exact fidelity matters, paste as plain text and re‑apply formats, or constrain allowed formats with the `formats` option.

### `getSemanticHTML()` output for nested lists isn't what I expect

**Cause.** Semantic HTML generation for deeply nested or mixed list types is an area with known rough edges.

**Solution.** If you rely on exact list HTML, post‑process the output, or render from the Delta directly with your own logic.

### Typing is slow in very large documents

**Cause.** Extremely large documents and heavy modules (e.g. syntax highlighting on huge code blocks) increase per‑keystroke work.

**Solution.** Increase `modules.syntax.interval`, split very large documents, or disable heavy modules where they are not needed.

### Toolbar button does nothing / `ignoring attaching to nonexistent format`

**Cause.** A toolbar control references a format that is not registered (common with the core build).

**Solution.** Register the format (e.g. `Quill.import('formats/bold')` then `Quill.register(...)`) or remove the control. The full build registers all standard formats.

### Read‑only editor still accepts programmatic edits

**Cause.** `readOnly`/`disable()` blocks **user** edits (`source: 'user'`), not API calls. This is by design.

**Solution.** This is expected — API methods still work so your code can update a read‑only view. To run an edit that should apply even to user‑blocked state, that is what the internal read‑only edit path is for; otherwise simply gate your own API calls.

### Editor loses selection after an API change

**Cause.** Some API changes move the selection. Quill attempts to preserve it, but complex updates may shift it.

**Solution.** Capture `quill.getSelection()` before the change and restore it with `quill.setSelection(range, 'silent')` afterward.

---

## 12. FAQ

**Q: Does Quill store content as HTML?**
No. The canonical format is **Delta** (a JSON op list). HTML is only for import (paste) and export (`getSemanticHTML()`). Store Deltas.

**Q: How do I get the HTML out of the editor?**
Use `quill.getSemanticHTML()` for clean, semantic HTML. (The editor's live DOM is an implementation detail and should not be scraped directly.)

**Q: How do I set content from saved data?**
`quill.setContents(savedDelta)`. For plain text, `quill.setText('...')`.

**Q: How do I make the editor read‑only?**
Set `readOnly: true` at construction, or call `quill.disable()` / `quill.enable()` at runtime.

**Q: How do I add a custom keyboard shortcut?**
`quill.keyboard.addBinding({ key: 'k', shortKey: true }, handler)`.

**Q: Can I limit which formats are allowed?**
Yes — pass `formats: ['bold', 'italic', ...]`. Anything not listed is stripped, including on paste.

**Q: Where do I put my own toolbar?**
Render the toolbar HTML yourself and point `modules.toolbar` at its selector, or pass an array config and let Quill build it.

**Q: Why isn't my theme styled?**
You set `theme` but didn't include the theme's CSS file. Add `quill.snow.css` or `quill.bubble.css`.

**Q: What's the difference between `format`, `formatText`, and `formatLine`?**
`format` applies to the current selection and auto‑detects inline vs. line scope; `formatText` targets a range with inline formats; `formatLine` targets whole lines with block formats.

**Q: How do I detect only human edits?**
Check `source === 'user'` in your `text-change` handler.

**Q: How do I insert an image?**
`quill.insertEmbed(index, 'image', url)`, or let the uploader module handle drag‑and‑drop / paste.

**Q: Does Quill support tables?**
Yes, via the `table` module (off by default). Enable it and use `quill.getModule('table')` for row/column operations.

**Q: How do I undo/redo programmatically?**
`quill.history.undo()` and `quill.history.redo()`.

**Q: Which browsers are supported?**
Modern browsers matching the `defaults` browserslist. Quill relies on standard DOM APIs (`MutationObserver`, `Selection`/`Range`, `beforeinput`).

**Q: How do I confine tooltips to a container?**
Set the `bounds` option to the element (or selector) you want them constrained within.

**Q: Is there a CDN?**
Yes — a jsDelivr‑backed CDN serves `quill.js`, `quill.core.js`, and the theme stylesheets.

---

## 13. API Quick Reference

### Constructor

```js
new Quill(container: string | HTMLElement, options?: QuillOptions)
```

### Content

| Method | Description |
|--------|-------------|
| `getText(index?, length?)` | Plain text of the document or a range. |
| `getContents(index?, length?)` | Delta of the document or a range. |
| `getLength()` | Document length (includes trailing `\n`). |
| `getSemanticHTML(index?, length?)` | Clean semantic HTML export. |
| `setText(text, source?)` | Replace contents with plain text. |
| `setContents(delta, source?)` | Replace contents with a Delta. |
| `updateContents(delta, source?)` | Apply a change Delta. |
| `insertText(index, text, [formats], source?)` | Insert text, optionally formatted. |
| `deleteText(index, length, source?)` | Delete a range. |
| `insertEmbed(index, type, value, source?)` | Insert an embed (image, video, formula). |

### Formatting

| Method | Description |
|--------|-------------|
| `format(name, value, source?)` | Format the current selection. |
| `formatText(index, length, name/formats, [value], source?)` | Apply inline format(s) to a range. |
| `formatLine(index, length, name/formats, [value], source?)` | Apply line format(s). |
| `removeFormat(index, length, source?)` | Strip formatting from a range. |
| `getFormat(index?/range?, length?)` | Formats of the selection or range. |

### Selection

| Method | Description |
|--------|-------------|
| `getSelection(focus?)` | Current `Range` (or `null`). |
| `setSelection(index, [length], source?)` | Set the selection/cursor. |
| `getBounds(index/range, length?)` | Pixel bounds of a position. |
| `focus()` / `blur()` | Focus / unfocus the editor. |
| `hasFocus()` | Whether the editor has focus. |
| `getLine(index)` / `getLeaf(index)` / `getLines(index?, length?)` | Inspect the document structure. |
| `getIndex(blot)` | Document index of a blot. |

### Editor state & events

| Method | Description |
|--------|-------------|
| `enable(enabled?)` / `disable()` | Toggle editability. |
| `isEnabled()` | Whether editing is allowed. |
| `update(source?)` | Force a synchronous update from the DOM. |
| `on(event, handler)` / `once(...)` / `off(...)` | Event subscription. |
| `getModule(name)` | Get a module instance. |
| `scrollSelectionIntoView()` | Scroll the selection into view. |

### Static members

| Member | Description |
|--------|-------------|
| `Quill.register(path, target, overwrite?)` | Register a format, module, or theme. |
| `Quill.import(name)` | Retrieve a registered component (`'delta'`, `'parchment'`, `'formats/...'`, etc.). |
| `Quill.find(node, bubble?)` | Find the Quill instance or blot for a DOM node. |
| `Quill.debug(level)` | Set global debug level (`'error'`, `'warn'`, `'log'`, `'info'`, or boolean). |
| `Quill.version` | The library version string. |
| `Quill.events` / `Quill.sources` | Event and source name constants. |

### Built‑in formats

Inline: `bold`, `italic`, `underline`, `strike`, `link`, `color`, `background`, `font`, `size`, `script` (sub/super), `code`.
Block/line: `header` (H1–H6), `blockquote`, `code-block`, `list` (`ordered`/`bullet`/`checked`/`unchecked`), `indent`, `align`, `direction`.
Embeds: `image`, `video`, `formula`, `table`.

---

_Generated June 2026 from slab/quill at 539cbff. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
