Jesse A. Green / notebook

Quill Rich Text Editor — User Guide

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.

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.

<!-- 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.

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)

<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)

<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:

npm install quill@2.0.3
added 1 package, and audited N packages in Xs

Import the full build, or the core build:

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():

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:

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

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.
// 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.

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 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:

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

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.

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

// 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

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

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

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 section for known edge cases with lists.

6.5 Formatting

// 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)

// 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

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

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)
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:

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.

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.

// 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',
});
// 2. Point at your own toolbar HTML
const quill = new Quill('#editor', {
  modules: { toolbar: '#my-toolbar' },
  theme: 'snow',
});
<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:

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:

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)

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:

// 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 library to be available on the page.

<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>
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.

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:

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

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.
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

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

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

10.3 Enforce a maximum length

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

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

10.5 Sync a hidden form field on submit

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

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.