Skip to content

name: quill description: Use when working with the Quill rich text editor. Covers initialization, configuration, API methods, Delta format, formats, modules, themes, registries, and customization.

Quill — Rich Text Editor

Quill is a modern rich text editor built for compatibility and extensibility. It uses a custom document model (Parchment) and represents content/changes as Deltas.

Installation

CDN (jsDelivr)

Full build (includes themes, common formats, modules):

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

Core build (minimal, no theme or formats):

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

npm

npm install quill
import Quill from 'quill';
// Or core only:
// import Quill from 'quill/core';

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

Import styles:

import "quill/dist/quill.core.css";
import "quill/dist/quill.snow.css";

Component Access

// Via Quill.import() (works in both CDN and npm)
const Delta = Quill.import('delta');
const Parchment = Quill.import('parchment');
const Link = Quill.import('formats/link');

// Or direct ES6 imports in npm
import { Delta } from 'quill';

Quickstart

<link href="https://cdn.jsdelivr.net/npm/quill@2/dist/quill.snow.css" rel="stylesheet">

<div id="editor">
  <p>Hello World!</p>
  <p>Some initial <strong>bold</strong> text</p>
  <p><br /></p>
</div>

<script src="https://cdn.jsdelivr.net/npm/quill@2/dist/quill.js"></script>
<script>
  const quill = new Quill('#editor', {
    theme: 'snow'
  });
</script>

Configuration

Pass options as the second argument to new Quill():

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

Options Reference

OptionDefaultDescription
boundsdocument.bodyDOM element or CSS selector to confine UI elements (tooltips, etc.)
debug'warn'Log level: 'error', 'warn', 'log', 'info', true (='log'), false
formatsnullArray of allowed format names. null allows all. Ignored if registry is set
placeholderNonePlaceholder text when editor is empty
readOnlyfalseInstantiate in read-only mode
registrynullCustom Parchment.Registry for isolated format sets
theme'snow'Theme name: 'snow', 'bubble', or falsy for minimal

Restricting Formats

const quill = new Quill('#editor', {
  formats: ['bold', 'italic', 'underline']
});

Core API

Content Methods

All content methods accept source: 'user', 'api', or 'silent'. User-source calls are ignored when the editor is disabled.

MethodSignatureDescription
deleteTextdeleteText(index, length, source='api'): DeltaDelete text at index
getContentsgetContents(index=0, length=remaining): DeltaGet editor contents as Delta
getLengthgetLength(): numberGet content length (empty editor = 1)
getTextgetText(index=0, length=remaining): stringGet plain text content
getSemanticHTMLgetSemanticHTML(index=0, length=remaining): stringGet HTML representation
insertEmbedinsertEmbed(index, type, value, source='api'): DeltaInsert embed (image, video, etc.)
insertTextinsertText(index, text, formats?, source='api'): DeltaInsert text with optional formatting
setContentssetContents(delta, source='api'): DeltaOverwrite editor with Delta
setTextsetText(text, source='api'): DeltaSet plain text content
updateContentsupdateContents(delta, source='api'): DeltaApply Delta changes to editor

Formatting Methods

MethodSignatureDescription
formatformat(name, value, source='api'): DeltaFormat at current selection. Sets active format if selection is collapsed
formatLineformatLine(index, length, format?, value?, source='api'): DeltaFormat lines in range
formatTextformatText(index, length, format?, value?, source='api'): DeltaFormat text in range
getFormatgetFormat(range?): Record<string, unknown>Get active formats at range or selection
removeFormatremoveFormat(index, length, source='api'): DeltaRemove all formatting in range

Selection Methods

MethodSignatureDescription
getBoundsgetBounds(index, length=0): {left, top, height, width}Pixel position of text at index
getSelectiongetSelection(focus=false): {index, length} | nullGet current selection range
setSelectionsetSelection(index, length=0, source='api')Set selection range
scrollSelectionIntoViewscrollSelectionIntoView()Scroll selection into visible area

Editor State

MethodSignatureDescription
blurblur()Remove focus
disabledisable()Disable user input
enableenable(enabled=true)Enable/disable user input
focusfocus({preventScroll?}={})Focus editor and restore last range
hasFocushasFocus(): booleanCheck if editor has focus
updateupdate(source='user')Synchronously check for updates and fire events

Events

quill.on('text-change', (delta, oldContents, source) => {
  // Content changed
});

quill.on('selection-change', (range, oldRange, source) => {
  // Selection changed. null range = focus lost
});

quill.on('editor-change', (eventName, ...args) => {
  // Fires for both text-change and selection-change, even with 'silent' source
});

Event methods: on(name, handler), once(name, handler), off(name, handler).

Delta Format

Deltas describe Quill documents and changes. They are a strict JSON subset.

Document Representation

{
  ops: [
    { insert: 'Gandalf', attributes: { bold: true } },
    { insert: ' the ' },
    { insert: 'Grey', attributes: { color: '#cccccc' } }
  ]
}

Key Rules

  • All Quill documents must end with a \n newline
  • Embeds use object insert: { insert: { image: 'url' } }
  • Line formatting applies to the newline character: { insert: '\n', attributes: { header: 1 } }
  • Use chainable methods: new Delta().insert('text').retain(5).delete(3)

Change Operations

OperationDescription
insertInsert text or embed
deleteDelete next N characters
retainKeep next N characters (optionally apply attributes)

Example Change

// Starting from "Gandalf the Grey"
new Delta()
  .retain(7, { bold: null, italic: true })  // Unbold and italicize "Gandalf"
  .retain(5)                                 // Keep " the "
  .insert('White', { color: '#fff' })       // Insert "White"
  .delete(4);                                // Delete "Grey"

Formats

Inline

  • background — Background color
  • bold — Bold
  • color — Text color
  • font — Font family
  • code — Inline code
  • italic — Italic
  • link — Hyperlink
  • size — Font size
  • strike — Strikethrough
  • script — Superscript/subscript ('super' / 'sub')
  • underline — Underline

Block

  • blockquote — Blockquote
  • header — Header (1–6)
  • indent — Indentation
  • list — List ('ordered', 'bullet', 'check')
  • align — Text alignment ('left', 'center', 'right', 'justify')
  • direction — Text direction ('rtl')
  • code-block — Code block

Embeds

  • formula — Formula (requires KaTeX)
  • image — Image
  • video — Video

Modules

Enable modules in configuration:

const quill = new Quill('#editor', {
  modules: {
    toolbar: true,
    history: { delay: 2500, userOnly: true },
    syntax: true
  }
});

Toolbar

// Simple array
const toolbarOptions = ['bold', 'italic', 'underline', 'strike'];

// Grouped
const toolbarOptions = [
  ['bold', 'italic'],
  ['link', 'image'],
  [{ header: [1, 2, 3, false] }],
  [{ list: 'ordered' }, { list: 'bullet' }],
  [{ color: [] }, { background: [] }],
  ['clean']
];

// Custom HTML container
const quill = new Quill('#editor', {
  modules: {
    toolbar: '#toolbar-id'
  }
});

// With custom handlers
const quill = new Quill('#editor', {
  modules: {
    toolbar: {
      container: '#toolbar',
      handlers: {
        link: function(value) {
          if (value) {
            const href = prompt('Enter URL');
            this.quill.format('link', href);
          } else {
            this.quill.format('link', false);
          }
        }
      }
    }
  }
});

History

const quill = new Quill('#editor', {
  modules: {
    history: {
      delay: 1000,      // Merge changes within N ms
      maxStack: 100,    // Max undo/redo stack size
      userOnly: false   // Only track user changes
    }
  }
});

quill.history.undo();
quill.history.redo();
quill.history.clear();
quill.history.cutoff();  // Prevent merging with previous change

Clipboard

// Add custom matcher
quill.clipboard.addMatcher('B', (node, delta) => {
  return delta.compose(new Delta().retain(delta.length(), { bold: true }));
});

quill.clipboard.addMatcher(Node.TEXT_NODE, (node, delta) => {
  return new Delta().insert(node.data);
});

// Paste HTML (dangerous — sanitize first!)
quill.clipboard.dangerouslyPasteHTML(5, '&nbsp;<b>World</b>');

Configuration:

const quill = new Quill('#editor', {
  modules: {
    clipboard: {
      matchers: [
        ['B', customMatcherA],
        [Node.TEXT_NODE, customMatcherB]
      ]
    }
  }
});

Keyboard

const quill = new Quill('#editor', {
  modules: {
    keyboard: {
      bindings: {
        tab: {
          key: 9,
          handler: function() { /* ... */ }
        }
      }
    }
  }
});

// Or after initialization
quill.keyboard.addBinding({ key: 'b', shortKey: true }, (range, context) => {
  this.quill.formatText(range, 'bold', true);
});

Context options for bindings: collapsed, empty, format, offset, prefix, suffix.

Syntax Highlighting

// CDN approach (highlight.js must be loaded globally)
const quill = new Quill('#editor', {
  modules: {
    syntax: true,
    toolbar: [['code-block']]
  }
});

// npm approach
import Quill from 'quill';
import hljs from 'highlight.js';

const quill = new Quill('#editor', {
  modules: {
    syntax: { hljs }
  }
});

Themes

Snow

Clean, flat toolbar theme:

<link href="https://cdn.jsdelivr.net/npm/quill@2/dist/quill.snow.css" rel="stylesheet">

Bubble

Tooltip-based theme:

<link href="https://cdn.jsdelivr.net/npm/quill@2/dist/quill.bubble.css" rel="stylesheet">

Custom / No Theme

For full UI customization, omit theme or set to falsy. Include minimal core CSS:

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/quill@2/dist/quill.core.css" />

Customization

Parchment Basics

Content and formats are represented as Blots (DOM-like nodes) or Attributors (attributes).

Using Class vs Inline Styles

const ColorClass = Quill.import('attributors/class/color');
const SizeStyle = Quill.import('attributors/style/size');
Quill.register(ColorClass, true);
Quill.register(SizeStyle, true);

Customizing Attributors

const FontAttributor = Quill.import('attributors/class/font');
FontAttributor.whitelist = ['sofia', 'slabo', 'roboto', 'inconsolata', 'ubuntu'];
Quill.register(FontAttributor, true);

Customizing Blots

const Bold = Quill.import('formats/bold');
Bold.tagName = 'B';  // Default is <strong>
Quill.register(Bold, true);

Extending Blots

const ListItem = Quill.import('formats/list/item');

class PlainListItem extends ListItem {
  formatAt(index, length, name, value) {
    if (name === 'list') {
      super.formatAt(index, length, name, value);
    }
    // Otherwise ignore
  }
}

Quill.register(PlainListItem, true);

Custom Modules

const Module = Quill.import('core/module');

class CustomModule extends Module {
  constructor(quill, options) {
    super(quill, options);
    // Module initialization
  }
}

Quill.register('modules/custom-module', CustomModule);

Registries

Use custom registries when multiple editors with different formats must coexist.

const Parchment = Quill.import('parchment');

// Essential formats
const Block = Quill.import('blots/block');
const Break = Quill.import('blots/break');
const Container = Quill.import('blots/container');
const Cursor = Quill.import('blots/cursor');
const Inline = Quill.import('blots/inline');
const Scroll = Quill.import('blots/scroll');
const Text = Quill.import('blots/text');

const registry = new Parchment.Registry();
registry.register(Scroll, Block, Break, Container, Cursor, Inline, Text);

// Add additional formats
const Bold = Quill.import('formats/bold');
registry.register(Bold);

const quill = new Quill('#editor', {
  registry,
  theme: 'snow'
});

Extension API

Static Methods

MethodSignatureDescription
Quill.findQuill.find(domNode, bubble=false): Blot | QuillGet Quill/Blot instance for DOM node
Quill.debugQuill.debug(level: string | boolean)Enable logging
Quill.importQuill.import(path): anyImport Quill library/format/module/theme
Quill.registerQuill.register(path, def, warn?)Register module/theme/format

Instance Methods

MethodSignatureDescription
getIndexgetIndex(blot): numberDocument index of blot
getLeafgetLeaf(index): [LeafBlot | null, number]Leaf blot at index
getLinegetLine(index): [Block | null, number]Line blot at index
getLinesgetLines(index?, length?): Block[]Lines in range
addContaineraddContainer(className, refNode?): ElementAdd container element
getModulegetModule(name): anyGet module instance

Upgrading to 2.0

Key changes from Quill 1.x:

  • TypeScript: Official TypeScript definitions (remove @types/quill)
  • SVG icons: Inlined in source (no SVG loader needed)
  • Removed: strict option, scrollingContainer option
  • Added: registry option for multiple editor format isolation
  • Clipboard: convert API changed; matchVisual and pasteHTML removed
  • Keyboard: key is case-sensitive; supports array of keys; native event passed to handlers
  • Parchment: Lists use <ol> only; code blocks use <div>; Attributor.Class → ClassAttributor; exports use full names (ScrollBlot, not Scroll)
  • Delta: Deprecated format support removed
  • Browser: IE support dropped