Mastering Quill Modules: From Toolbar Basics to Custom Extensions

This article introduces the Quill rich‑text editor, explains its built‑in modules, details the toolbar module loading process, and walks through creating a custom counter module, providing code examples and configuration tips for extending Quill in frontend projects.

Huawei Cloud Developer Alliance
Huawei Cloud Developer Alliance
Huawei Cloud Developer Alliance
Mastering Quill Modules: From Toolbar Basics to Custom Extensions

Introduction

EditorX is a powerful rich‑text editor developed by DevUI, built on top of Quill, an open‑source, API‑driven web editor with more than 25k GitHub stars. The article first explains what Quill modules are and how to configure them, then analyzes the module execution mechanism and communication with Quill, and finally demonstrates how to create a custom Quill module to extend the editor.

Quill Module Overview

Developers familiar with Quill know that modules can be configured, for example, to customize the toolbar:

var quill = new Quill('#editor', {
  theme: 'snow',
  modules: {
    toolbar: [['bold', 'italic'], ['link', 'image']]
  }
});

The modules option configures the toolbar, which renders four toolbar buttons.

Toolbar Module as a JavaScript Class

A module is a normal JavaScript class with a constructor, member variables, and methods. The Toolbar module’s source structure looks like this:

class Toolbar {
  constructor(quill, options) {
    super(quill, options);
    if (Array.isArray(this.options.container)) {
      const container = document.createElement('div');
      addControls(container, this.options.container);
      quill.container.parentNode.insertBefore(container, quill.container);
      this.container = container;
    } else {
      ...
    }
    this.container.classList.add('ql-toolbar');
    this.controls = [];
    this.handlers = {};
    Object.keys(this.options.handlers).forEach(format => {
      this.addHandler(format, this.options.handlers[format]);
    });
    Array.from(this.container.querySelectorAll('button, select')).forEach(input => {
      this.attach(input);
    });
    ...
  }
  addHandler(format, handler) {
    this.handlers[format] = handler;
  }
  ...
}

The constructor receives the Quill instance and the module options, builds the toolbar container, adds CSS classes, registers handlers, and binds events.

Built‑in Modules

Clipboard – handles copy/paste and HTML‑to‑Delta conversion.

History – maintains an operation stack for undo/redo.

Keyboard – configures keyboard shortcuts.

Syntax – provides code syntax highlighting (requires highlight.js).

Toolbar – renders the toolbar UI.

Uploader – handles file uploads.

Clipboard, History, and Keyboard are required and always enabled.

Module Configuration Example

To add a custom shortcut for strikethrough (Ctrl+Shift+S), configure the keyboard module as follows:

modules: {
  keyboard: {
    bindings: {
      strike: {
        key: 'S',
        ctrlKey: true,
        shiftKey: true,
        handler: function(range, context) {
          const format = this.quill.getFormat(range);
          this.quill.format('strike', !format.strike);
        }
      }
    }
  },
  toolbar: [['bold', 'italic'], ['link', 'image']]
}

All modules are passed through the modules option when initializing Quill.

Module Loading Mechanism

When new Quill() is called, the constructor expands the configuration, creates the theme instance, and loads the three required modules (keyboard, clipboard, history). The theme’s init() method then iterates over options.modules, calling addModule(name) for each custom module.

init() {
  Object.keys(this.options.modules).forEach(name => {
    if (this.modules[name] == null) {
      this.addModule(name);
    }
  });
}

addModule(name) {
  const ModuleClass = this.quill.constructor.import(`modules/${name}`);
  this.modules[name] = new ModuleClass(this.quill, this.options.modules[name] || {});
  return this.modules[name];
}

For the toolbar module, addModule creates a Toolbar instance, which parses the configuration, builds the button container, and binds events such as the link shortcut.

extendToolbar(toolbar) {
  toolbar.container.classList.add('ql-snow');
  this.buildButtons(toolbar.container.querySelectorAll('button'), icons);
  this.buildPickers(toolbar.container.querySelectorAll('select'), icons);
  this.tooltip = new SnowTooltip(this.quill, this.options.bounds);
  if (toolbar.container.querySelector('.ql-link')) {
    this.quill.keyboard.addBinding({ key: 'k', shortKey: true }, (range, context) => {
      toolbar.handlers.link.call(toolbar, !context.format.link);
    });
  }
}

Creating a Custom Module

To illustrate custom module development, a simple counter module that displays the current character count is created.

class Counter {
  constructor(quill, options) {
    this.container = quill.addContainer('ql-counter');
    quill.on(Quill.events.TEXT_CHANGE, () => {
      const text = quill.getText();
      const char = text.replace(/\s/g, '');
      this.container.innerHTML = `当前字数:${char.length}`;
    });
  }
}
export default Counter;

Before using the module, it must be registered with Quill:

import Quill from 'quill';
import Counter from './counter';
Quill.register('modules/counter', Counter);

Then enable it in the configuration:

modules: {
  toolbar: [['bold', 'italic'], ['link', 'image']],
  counter: true
}

The counter appears below the editor and updates in real time as the user types.

Summary

The article first introduced two simple examples of configuring Quill modules, then dissected Quill’s initialization to reveal the module loading mechanism, with a detailed focus on the toolbar module. Finally, it demonstrated how to develop a custom counter module, showing how developers can extend rich‑text functionality.

JavaScriptfrontend developmentrich-text-editorQuillToolbarCustom Modules
Huawei Cloud Developer Alliance
Written by

Huawei Cloud Developer Alliance

The Huawei Cloud Developer Alliance creates a tech sharing platform for developers and partners, gathering Huawei Cloud product knowledge, event updates, expert talks, and more. Together we continuously innovate to build the cloud foundation of an intelligent world.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.