` to better support syntax highlighting.
* Static `register` method added to allow dependent chains of registration.
* Static `formats` method now passes in `scroll`.
* Blot constructor now requires `scroll` to be passed in.
* Attributors are exported as top-level classes.
Instead of accessing class attributor via `Parchment.Attributor.Class`, you now use it at `Parchment.ClassAttributor`. Similarly, `Parchment.Attributor.Style` is now `Parchment.StyleAttributor`, and `Parchment.Attributor.Attribute` is now `Parchment.Attributor`.
* Exports are using full names.
Instead of `Parchment.Scroll`, you now use `Parchment.ScrollBlot`. The similar change applies to `Parchment.Embed`, `Parchment.Text`, `Parchment.Block`, `Parchment.Inline`, and more.
[](https://quilljs.com/docs/upgrading-to-2-0#delta)
Delta
---------------------------------------------------------
* Support for the deprecated delta format, where embeds had integer values and list attributes had different keys, is now removed
[](https://quilljs.com/docs/upgrading-to-2-0#browser)
Browser
-------------------------------------------------------------
* Internet Explorer support is dropped.
* * *
An Open Source Project
----------------------
Quill is developed and maintained by [Slab](https://slab.com/)
. It is permissively licensed under BSD. Use it freely in personal or commercial projects!
[Star](https://github.com/slab/quill/ "Star Quill on GitHub")
[37,622](https://github.com/slab/quill/stargazers "Quill Stargazers")
---
# Themes - Quill Rich Text Editor
Document Navigation
* [Quickstart](https://quilljs.com/docs/quickstart)
* [Why Quill](https://quilljs.com/docs/why-quill)
* [Installation](https://quilljs.com/docs/installation)
* [Upgrading to 2.0](https://quilljs.com/docs/upgrading-to-2-0)
* [Configuration](https://quilljs.com/docs/configuration)
* [Formats](https://quilljs.com/docs/formats)
* [API](https://quilljs.com/docs/api)
* [Content](https://quilljs.com/docs/api#content)
* [Formatting](https://quilljs.com/docs/api#formatting)
* [Selection](https://quilljs.com/docs/api#selection)
* [Editor](https://quilljs.com/docs/api#editor)
* [Events](https://quilljs.com/docs/api#events)
* [Model](https://quilljs.com/docs/api#model)
* [Extension](https://quilljs.com/docs/api#extension)
* [Delta](https://quilljs.com/docs/delta)
* [Modules](https://quilljs.com/docs/modules)
* [Toolbar](https://quilljs.com/docs/modules/toolbar)
* [Keyboard](https://quilljs.com/docs/modules/keyboard)
* [History](https://quilljs.com/docs/modules/history)
* [Clipboard](https://quilljs.com/docs/modules/clipboard)
* [Syntax](https://quilljs.com/docs/modules/syntax)
* [Customization](https://quilljs.com/docs/customization)
* [Themes](https://quilljs.com/docs/customization/themes)
* [Registries](https://quilljs.com/docs/customization/registries)
* [Guides](https://quilljs.com/docs/guides/designing-the-delta-format)
* [Designing the Delta Format](https://quilljs.com/docs/guides/designing-the-delta-format)
* [Building a Custom Module](https://quilljs.com/docs/guides/building-a-custom-module)
* [Cloning Medium with Parchment](https://quilljs.com/docs/guides/cloning-medium-with-parchment)
DocumentationThemes
[Edit page on GitHub ↗](https://github.com/slab/quill/tree/main/packages/website/content/docs/customization/themes.mdx "Edit on GitHub")
Themes
======
Themes allow you to easily make your editor look good with minimal effort. Quill features two officially supported themes: [Snow](https://quilljs.com/docs/customization/themes#snow)
and [Bubble](https://quilljs.com/docs/customization/themes#bubble)
.
### [](https://quilljs.com/docs/customization/themes#usage)
Usage
[](https://quilljs.com/docs/customization/themes#bubble)
Bubble
---------------------------------------------------------------
Bubble is a simple tooltip based theme.
Show Code
[Standalone](https://quilljs.com/standalone/bubble/)
[](https://quilljs.com/docs/customization/themes#snow)
Snow
-----------------------------------------------------------
Snow is a clean, flat toolbar theme.
Show Code
[Standalone](https://quilljs.com/standalone/snow/)
### [](https://quilljs.com/docs/customization/themes#customization)
Customization
Themes primarily control the visual look of Quill through its CSS stylesheet, and many changes can easily be made by overriding these rules. This is easiest to do, as with any other web application, by simply using your browser developer console to inspect the elements to view the rules affecting them.
Many other customizations can be done through the respective modules. For example, the toolbar is perhaps the most visible user interface, but much of the customization is done through the [Toolbar module](https://quilljs.com/docs/modules/toolbar)
.
* * *
An Open Source Project
----------------------
Quill is developed and maintained by [Slab](https://slab.com/)
. It is permissively licensed under BSD. Use it freely in personal or commercial projects!
[Star](https://github.com/slab/quill/ "Star Quill on GitHub")
[37,622](https://github.com/slab/quill/stargazers "Quill Stargazers")
---
# Modules - Quill Rich Text Editor
Document Navigation
* [Quickstart](https://quilljs.com/docs/quickstart)
* [Why Quill](https://quilljs.com/docs/why-quill)
* [Installation](https://quilljs.com/docs/installation)
* [Upgrading to 2.0](https://quilljs.com/docs/upgrading-to-2-0)
* [Configuration](https://quilljs.com/docs/configuration)
* [Formats](https://quilljs.com/docs/formats)
* [API](https://quilljs.com/docs/api)
* [Content](https://quilljs.com/docs/api#content)
* [Formatting](https://quilljs.com/docs/api#formatting)
* [Selection](https://quilljs.com/docs/api#selection)
* [Editor](https://quilljs.com/docs/api#editor)
* [Events](https://quilljs.com/docs/api#events)
* [Model](https://quilljs.com/docs/api#model)
* [Extension](https://quilljs.com/docs/api#extension)
* [Delta](https://quilljs.com/docs/delta)
* [Modules](https://quilljs.com/docs/modules)
* [Toolbar](https://quilljs.com/docs/modules/toolbar)
* [Keyboard](https://quilljs.com/docs/modules/keyboard)
* [History](https://quilljs.com/docs/modules/history)
* [Clipboard](https://quilljs.com/docs/modules/clipboard)
* [Syntax](https://quilljs.com/docs/modules/syntax)
* [Customization](https://quilljs.com/docs/customization)
* [Themes](https://quilljs.com/docs/customization/themes)
* [Registries](https://quilljs.com/docs/customization/registries)
* [Guides](https://quilljs.com/docs/guides/designing-the-delta-format)
* [Designing the Delta Format](https://quilljs.com/docs/guides/designing-the-delta-format)
* [Building a Custom Module](https://quilljs.com/docs/guides/building-a-custom-module)
* [Cloning Medium with Parchment](https://quilljs.com/docs/guides/cloning-medium-with-parchment)
DocumentationModules
[Edit page on GitHub ↗](https://github.com/slab/quill/tree/main/packages/website/content/docs/modules.mdx "Edit on GitHub")
Modules
=======
Modules allow Quill's behavior and functionality to be customized. Several officially supported modules are available to pick and choose from, some with additional configuration options and APIs. Refer to their respective documentation pages for more details.
To enable a module, simply include it in Quill's configuration.
const quill = new Quill('#editor', { modules: { history: { // Enable with custom configurations delay: 2500, userOnly: true }, syntax: true // Enable with default configuration }});
The [Clipboard](https://quilljs.com/docs/modules/clipboard)
, [Keyboard](https://quilljs.com/docs/modules/keyboard)
, and [History](https://quilljs.com/docs/modules/history)
modules are required by Quill and do not need to be included explictly, but may be configured like any other module.
[](https://quilljs.com/docs/modules#extending)
Extending
--------------------------------------------------------
Modules may also be extended and re-registered, replacing the original module. Even required modules may be re-registered and replaced.
const Clipboard = Quill.import('modules/clipboard');const Delta = Quill.import('delta');
class PlainClipboard extends Clipboard { convert(html = null) { if (typeof html === 'string') { this.container.innerHTML = html; } let text = this.container.innerText; this.container.innerHTML = ''; return new Delta().insert(text); }}
Quill.register('modules/clipboard', PlainClipboard, true);
// Will be created with instance of PlainClipboardconst quill = new Quill('#editor');
Note
This particular example was selected to show what is possible. It is often easier to just use an API or configuration the existing module exposes. In this example, the existing Clipboard's [addMatcher](https://quilljs.com/docs/modules/clipboard#addmatcher)
API is suitable for most paste customization scenarios.
* * *
An Open Source Project
----------------------
Quill is developed and maintained by [Slab](https://slab.com/)
. It is permissively licensed under BSD. Use it freely in personal or commercial projects!
[Star](https://github.com/slab/quill/ "Star Quill on GitHub")
[37,622](https://github.com/slab/quill/stargazers "Quill Stargazers")
---
# Registries - Quill Rich Text Editor
Document Navigation
* [Quickstart](https://quilljs.com/docs/quickstart)
* [Why Quill](https://quilljs.com/docs/why-quill)
* [Installation](https://quilljs.com/docs/installation)
* [Upgrading to 2.0](https://quilljs.com/docs/upgrading-to-2-0)
* [Configuration](https://quilljs.com/docs/configuration)
* [Formats](https://quilljs.com/docs/formats)
* [API](https://quilljs.com/docs/api)
* [Content](https://quilljs.com/docs/api#content)
* [Formatting](https://quilljs.com/docs/api#formatting)
* [Selection](https://quilljs.com/docs/api#selection)
* [Editor](https://quilljs.com/docs/api#editor)
* [Events](https://quilljs.com/docs/api#events)
* [Model](https://quilljs.com/docs/api#model)
* [Extension](https://quilljs.com/docs/api#extension)
* [Delta](https://quilljs.com/docs/delta)
* [Modules](https://quilljs.com/docs/modules)
* [Toolbar](https://quilljs.com/docs/modules/toolbar)
* [Keyboard](https://quilljs.com/docs/modules/keyboard)
* [History](https://quilljs.com/docs/modules/history)
* [Clipboard](https://quilljs.com/docs/modules/clipboard)
* [Syntax](https://quilljs.com/docs/modules/syntax)
* [Customization](https://quilljs.com/docs/customization)
* [Themes](https://quilljs.com/docs/customization/themes)
* [Registries](https://quilljs.com/docs/customization/registries)
* [Guides](https://quilljs.com/docs/guides/designing-the-delta-format)
* [Designing the Delta Format](https://quilljs.com/docs/guides/designing-the-delta-format)
* [Building a Custom Module](https://quilljs.com/docs/guides/building-a-custom-module)
* [Cloning Medium with Parchment](https://quilljs.com/docs/guides/cloning-medium-with-parchment)
DocumentationRegistries
[Edit page on GitHub ↗](https://github.com/slab/quill/tree/main/packages/website/content/docs/customization/registries.mdx "Edit on GitHub")
Registries
==========
Registries allow multiple editors with different formats to coexist on the same page.
If you register a format with `Quill.register()`, the format will be registered to a global registry, which will be used by all Quill instances.
However, in some cases, you might want to have multiple registries, so that different Quill instances can have different formats. For example, you might want to have a Quill instance that only supports bold and italic, and another Quill instance that supports bold, italic, and underline.
[](https://quilljs.com/docs/customization/registries#usage)
Usage
-----------------------------------------------------------------
To create a Quill with a custom registry, you can pass in a registry object to the Quill constructor:
const registry = new Parchment.Registry();
// Register the formats that you need for the editor with `registry.register()`.// We will cover this in more detail in the next section.
const quill = new Quill('#editor', { registry, // ...other options})
[](https://quilljs.com/docs/customization/registries#register-formats)
Register Formats
---------------------------------------------------------------------------------------
A custom registry doesn't come with any formats by default. You should register the formats that you need with `registry.register()`. There are some essential formats that you will need to register in order to have a functional editor:
index.htmlindex.js
Hide Result
Note
You may have noticed that the format buttons on the toolbar above doesn't work. This is because we haven't registered any of the corresponding formats yet.
The toolbar module doesn't detect whether a format is available or not, so it will always show the buttons. Follow [this guide](https://quilljs.com/docs/modules/toolbar)
to learn more about how to customize the toolbar.
* * *
An Open Source Project
----------------------
Quill is developed and maintained by [Slab](https://slab.com/)
. It is permissively licensed under BSD. Use it freely in personal or commercial projects!
[Star](https://github.com/slab/quill/ "Star Quill on GitHub")
[37,622](https://github.com/slab/quill/stargazers "Quill Stargazers")
---
# Configuration - Quill Rich Text Editor
Document Navigation
* [Quickstart](https://quilljs.com/docs/quickstart)
* [Why Quill](https://quilljs.com/docs/why-quill)
* [Installation](https://quilljs.com/docs/installation)
* [Upgrading to 2.0](https://quilljs.com/docs/upgrading-to-2-0)
* [Configuration](https://quilljs.com/docs/configuration)
* [Formats](https://quilljs.com/docs/formats)
* [API](https://quilljs.com/docs/api)
* [Content](https://quilljs.com/docs/api#content)
* [Formatting](https://quilljs.com/docs/api#formatting)
* [Selection](https://quilljs.com/docs/api#selection)
* [Editor](https://quilljs.com/docs/api#editor)
* [Events](https://quilljs.com/docs/api#events)
* [Model](https://quilljs.com/docs/api#model)
* [Extension](https://quilljs.com/docs/api#extension)
* [Delta](https://quilljs.com/docs/delta)
* [Modules](https://quilljs.com/docs/modules)
* [Toolbar](https://quilljs.com/docs/modules/toolbar)
* [Keyboard](https://quilljs.com/docs/modules/keyboard)
* [History](https://quilljs.com/docs/modules/history)
* [Clipboard](https://quilljs.com/docs/modules/clipboard)
* [Syntax](https://quilljs.com/docs/modules/syntax)
* [Customization](https://quilljs.com/docs/customization)
* [Themes](https://quilljs.com/docs/customization/themes)
* [Registries](https://quilljs.com/docs/customization/registries)
* [Guides](https://quilljs.com/docs/guides/designing-the-delta-format)
* [Designing the Delta Format](https://quilljs.com/docs/guides/designing-the-delta-format)
* [Building a Custom Module](https://quilljs.com/docs/guides/building-a-custom-module)
* [Cloning Medium with Parchment](https://quilljs.com/docs/guides/cloning-medium-with-parchment)
DocumentationConfiguration
[Edit page on GitHub ↗](https://github.com/slab/quill/tree/main/packages/website/content/docs/configuration.mdx "Edit on GitHub")
Configuration
=============
Quill allows several ways to customize it to suit your needs. This section is dedicated to tweaking existing functionality. See the [Modules](https://quilljs.com/docs/modules)
section for adding new functionality and the [Themes](https://quilljs.com/docs/themes)
section for styling.
[](https://quilljs.com/docs/configuration#container)
Container
--------------------------------------------------------------
Quill requires a container where the editor will be appended. You can pass in either a CSS selector or a DOM object.
const quill = new Quill('#editor'); // First matching element will be used
const container = document.getElementById('editor');const quill = new Quill(container);
If the container is not empty, Quill will initialize with the existing contents.
[](https://quilljs.com/docs/configuration#options)
Options
----------------------------------------------------------
To configure Quill, pass in an options object:
const options = {
debug: 'info',
modules: {
toolbar: true,
},
placeholder: 'Compose an epic...',
theme: 'snow'
};
const quill = new Quill('#editor', options);
Run Code
The following keys are recognized:
### [](https://quilljs.com/docs/configuration#bounds)
bounds
Default: `document.body`
DOM Element or a CSS selector for a DOM Element, within which the editor's ui elements (i.e. tooltips, etc.) should be confined. Currently, it only considers left and right boundaries.
### [](https://quilljs.com/docs/configuration#debug)
debug
Default: `warn`
Shortcut for [debug](https://quilljs.com/docs/api#debug)
. Note `debug` is a static method and will affect other instances of Quill editors on the page. Only warning and error messages are enabled by default.
### [](https://quilljs.com/docs/configuration#formats)
formats
Default: `null`
A list of formats that are recognized and can exist within the editor contents.
By default, all formats that are defined in the Quill library are allowed. To restrict formatting to a smaller list, pass in an array of the format names to support.
You can create brand new formats or more fully customize the content using [Registries](https://quilljs.com/docs/registries)
. Specifying a `registry` option will ignore this `formats` option.
index.htmlindex.js
const Parchment = Quill.import('parchment');
const quill = new Quill('#editor', {
formats: \['italic'\],
});
const Delta = Quill.import('delta');
quill.setContents(
new Delta()
.insert('Only ')
.insert('italic', { italic: true })
.insert(' is allowed. ')
.insert('Bold', { bold: true })
.insert(' is not.')
);
Hide Result
### [](https://quilljs.com/docs/configuration#placeholder)
placeholder
Default: None
Placeholder text to show when editor is empty.
const options = {
placeholder: 'Hello, World!',
theme: 'snow'
};
const quill = new Quill('#editor', options);
Run Code
### [](https://quilljs.com/docs/configuration#readonly)
readOnly
Default: `false`
Whether to instantiate the editor to read-only mode.
const options = {
readOnly: true,
modules: {
toolbar: null
},
theme: 'snow'
};
const quill = new Quill('#editor', options);
const Delta = Quill.import('delta');
quill.setContents(
new Delta()
.insert('Hello, ')
.insert('World', { bold: true })
.insert('\\n')
);
Run Code
### [](https://quilljs.com/docs/configuration#registry)
registry
Default: `null`
By default all formats defined by Quill are supported in the editor contents through a shared registry between editor instances. Use `formats` to restrict formatting for simple use cases and `registry` for greater customization. Specifying this `registry` option will ignore the `formatting` option. Learn more about [Registries](https://quilljs.com/docs/registries)
.
### [](https://quilljs.com/docs/configuration#theme)
theme
Name of theme to use. The builtin options are `"bubble"` or `"snow"`. An invalid or falsy value will load a default minimal theme. Note the theme's specific stylesheet still needs to be included manually. See [Themes](https://quilljs.com/docs/themes)
for more information.
* * *
An Open Source Project
----------------------
Quill is developed and maintained by [Slab](https://slab.com/)
. It is permissively licensed under BSD. Use it freely in personal or commercial projects!
[Star](https://github.com/slab/quill/ "Star Quill on GitHub")
[37,622](https://github.com/slab/quill/stargazers "Quill Stargazers")
---
# Syntax Highlighter Module - Quill Rich Text Editor
Document Navigation
* [Quickstart](https://quilljs.com/docs/quickstart)
* [Why Quill](https://quilljs.com/docs/why-quill)
* [Installation](https://quilljs.com/docs/installation)
* [Upgrading to 2.0](https://quilljs.com/docs/upgrading-to-2-0)
* [Configuration](https://quilljs.com/docs/configuration)
* [Formats](https://quilljs.com/docs/formats)
* [API](https://quilljs.com/docs/api)
* [Content](https://quilljs.com/docs/api#content)
* [Formatting](https://quilljs.com/docs/api#formatting)
* [Selection](https://quilljs.com/docs/api#selection)
* [Editor](https://quilljs.com/docs/api#editor)
* [Events](https://quilljs.com/docs/api#events)
* [Model](https://quilljs.com/docs/api#model)
* [Extension](https://quilljs.com/docs/api#extension)
* [Delta](https://quilljs.com/docs/delta)
* [Modules](https://quilljs.com/docs/modules)
* [Toolbar](https://quilljs.com/docs/modules/toolbar)
* [Keyboard](https://quilljs.com/docs/modules/keyboard)
* [History](https://quilljs.com/docs/modules/history)
* [Clipboard](https://quilljs.com/docs/modules/clipboard)
* [Syntax](https://quilljs.com/docs/modules/syntax)
* [Customization](https://quilljs.com/docs/customization)
* [Themes](https://quilljs.com/docs/customization/themes)
* [Registries](https://quilljs.com/docs/customization/registries)
* [Guides](https://quilljs.com/docs/guides/designing-the-delta-format)
* [Designing the Delta Format](https://quilljs.com/docs/guides/designing-the-delta-format)
* [Building a Custom Module](https://quilljs.com/docs/guides/building-a-custom-module)
* [Cloning Medium with Parchment](https://quilljs.com/docs/guides/cloning-medium-with-parchment)
DocumentationSyntax Highlighter Module
[Edit page on GitHub ↗](https://github.com/slab/quill/tree/main/packages/website/content/docs/modules/syntax.mdx "Edit on GitHub")
Syntax Highlighter Module
=========================
The Syntax Module enhances the Code Block format by automatically detecting and applying syntax highlighting. The excellent [highlight.js](https://highlightjs.org/)
library is used as a dependency to parse and tokenize code blocks.
In general, you may [configure](https://highlightjs.readthedocs.io/en/latest/api.html#configure-options)
highlight.js as needed. However, Quill expects and requires the `useBR` option to be `false` if you are using highlight.js < v11.
Quill supports highlight.js v9.12.0 and later.
### [](https://quilljs.com/docs/modules/syntax#usage)
Usage
Hide Result
### [](https://quilljs.com/docs/modules/syntax#use-npm-package)
Use npm Package
If you install highlight.js as an npm package and don't want to expose it to `window`, you need to pass it to syntax module as an option:
import Quill from 'quill';import hljs from 'highlight.js';
const quill = new Quill('#editor', { modules: { syntax: { hljs }, },});
* * *
An Open Source Project
----------------------
Quill is developed and maintained by [Slab](https://slab.com/)
. It is permissively licensed under BSD. Use it freely in personal or commercial projects!
[Star](https://github.com/slab/quill/ "Star Quill on GitHub")
[37,622](https://github.com/slab/quill/stargazers "Quill Stargazers")
---
# Customization - Quill Rich Text Editor
Document Navigation
* [Quickstart](https://quilljs.com/docs/quickstart)
* [Why Quill](https://quilljs.com/docs/why-quill)
* [Installation](https://quilljs.com/docs/installation)
* [Upgrading to 2.0](https://quilljs.com/docs/upgrading-to-2-0)
* [Configuration](https://quilljs.com/docs/configuration)
* [Formats](https://quilljs.com/docs/formats)
* [API](https://quilljs.com/docs/api)
* [Content](https://quilljs.com/docs/api#content)
* [Formatting](https://quilljs.com/docs/api#formatting)
* [Selection](https://quilljs.com/docs/api#selection)
* [Editor](https://quilljs.com/docs/api#editor)
* [Events](https://quilljs.com/docs/api#events)
* [Model](https://quilljs.com/docs/api#model)
* [Extension](https://quilljs.com/docs/api#extension)
* [Delta](https://quilljs.com/docs/delta)
* [Modules](https://quilljs.com/docs/modules)
* [Toolbar](https://quilljs.com/docs/modules/toolbar)
* [Keyboard](https://quilljs.com/docs/modules/keyboard)
* [History](https://quilljs.com/docs/modules/history)
* [Clipboard](https://quilljs.com/docs/modules/clipboard)
* [Syntax](https://quilljs.com/docs/modules/syntax)
* [Customization](https://quilljs.com/docs/customization)
* [Themes](https://quilljs.com/docs/customization/themes)
* [Registries](https://quilljs.com/docs/customization/registries)
* [Guides](https://quilljs.com/docs/guides/designing-the-delta-format)
* [Designing the Delta Format](https://quilljs.com/docs/guides/designing-the-delta-format)
* [Building a Custom Module](https://quilljs.com/docs/guides/building-a-custom-module)
* [Cloning Medium with Parchment](https://quilljs.com/docs/guides/cloning-medium-with-parchment)
DocumentationCustomization
[Edit page on GitHub ↗](https://github.com/slab/quill/tree/main/packages/website/content/docs/customization.mdx "Edit on GitHub")
Customization
=============
Quill was designed with customization and extension in mind. This is achieved by implementing a small editor core exposed by a granular, well defined [API](https://quilljs.com/docs/api)
. The core is augmented by [modules](https://quilljs.com/docs/modules)
, using the same [APIs](https://quilljs.com/docs/api)
you have access to.
In general, common customizations are handled in [configurations](https://quilljs.com/docs/customization#configurations/)
, user interfaces by [Themes](https://quilljs.com/docs/customization#themes)
and CSS, functionality by [modules](https://quilljs.com/docs/customization#modules)
, and editor contents by [Parchment](https://quilljs.com/docs/customization#content-and-formatting)
.
### [](https://quilljs.com/docs/customization#configurations)
Configurations
Quill favors Code over Configuration™, but for very common needs, especially where the equivalent code would be lengthy or complex, Quill provides [configuration](https://quilljs.com/docs/configuration)
options. This would be a good first place to look to determine if you even need to implement any custom functionality.
Two of the most powerful options is `modules` and `theme`. You can drastically change or expand what Quill can and does do by simply adding or removing individual [modules](https://quilljs.com/docs/modules)
or using a different [theme](https://quilljs.com/docs/themes)
.
### [](https://quilljs.com/docs/customization#themes)
Themes
Quill officially supports a standard toolbar theme [Snow](https://quilljs.com/docs/themes#snow)
and a floating tooltip theme [Bubble](https://quilljs.com/docs/themes#bubble)
. Since Quill is not confined within an iframe like many legacy editors, many visual modifications can be made with just CSS, using one of the existing themes.
If you would like to drastically change UI interactions, you can omit the `theme` configuration option, which will give you an unstyled Quill editor. You do still need to include a minimal stylesheet that, for example, makes sure spaces render in all browsers and ordered lists are appropriately numbered.
From there you can implement and attach your own UI elements like custom dropdowns or tooltips. The last section of [Cloning Medium with Parchment](https://quilljs.com/guides/cloning-medium-with-parchment#final-polish)
provides an example of this in action.
### [](https://quilljs.com/docs/customization#modules)
Modules
Quill is designed with a modular architecture composed of a small editing core, surrounded by modules that augment its functionality. Some of this functionality is quite integral to editing, such as the [History](https://quilljs.com/docs/modules/history)
module, which manages undo and redo. Because all modules use the same public [API](https://quilljs.com/docs/api)
exposed to the developer, even interchanging core modules is possible, when necessary.
Like Quill's core itself, many [modules](https://quilljs.com/docs/modules)
expose additional configuration options and APIs. Before deciding to replace a module, take a look at its documentation. Often your desired customization is already implemented as a configuration or API call.
Otherwise, if you would like to drastically change functionality an existing module already covers, you can simply not include it—or explicitly exclude it when a theme includes it by default—and implement the functionality to your liking external to Quill, using the same [API](https://quilljs.com/docs/api)
the default module uses.
const quill = new Quill('#editor', {
modules: {
toolbar: false // Snow includes toolbar by default
},
theme: 'snow'
});
Run Code
A few modules—[Clipboard](https://quilljs.com/docs/modules/clipboard)
, [Keyboard](https://quilljs.com/docs/modules/keyboard)
, and [History](https://quilljs.com/docs/modules/history)
—need to be included as core functionality depend on the APIs they provide. For example, even though undo and redo is basic, expected, editor functionality, the native browser behavior handling of this is inconsistent and unpredictable. The History module bridges the gap by implementing its own undo manager and exposing `undo()` and `redo()` as APIs.
Nevertheless, staying true to Quill modular design, you can still drastically change the way undo and redo—or any other core functionality—works by implementing your own undo manager to replace the History module. As long as you implement the same API interface, Quill will happily use your replacement module. This is most easily done by inheriting from the existing module, and overwriting the methods you want to change. Take a look at the [modules](https://quilljs.com/docs/modules)
documentation for a very simple example of overwriting the core [Clipboard](https://quilljs.com/docs/modules/clipboard)
module.
Finally, you may want to add functionality not provided by existing modules. In this case, it may be convenient to organize this as a Quill module, which the [Building A Custom Module](https://quilljs.com/guides/building-a-custom-module)
guide covers. Of course, it is certainly valid to just keep this logic separate from Quill, in your application code instead.
### [](https://quilljs.com/docs/customization#content-and-formatting)
Content and Formatting
Quill allows modification and extension of the contents and formats that it understands through its document model [Parchment](https://github.com/quilljs/parchment/)
. Content and formats are represented in Parchment as either Blots or Attributors, which roughly correspond to Nodes or Attributes in the DOM.
#### [](https://quilljs.com/docs/customization#class-vs-inline)
Class vs Inline
Quill uses classes, instead of inline style attributes, when possible, but both are implemented for you to pick and choose. A live example of this is implemented as a [Playground snippet](https://quilljs.com/playground#class-vs-inline-style)
.
const ColorClass = Quill.import('attributors/class/color');const SizeStyle = Quill.import('attributors/style/size');Quill.register(ColorClass, true);Quill.register(SizeStyle, true);
// Initialize as you would normallyconst quill = new Quill('#editor', { modules: { toolbar: true, }, theme: 'snow',});
#### [](https://quilljs.com/docs/customization#customizing-attributors)
Customizing Attributors
In addition to choosing the particular Attributor, you can also customize existing ones. Here is an example of the font whitelist to add additional fonts.
const FontAttributor = Quill.import('attributors/class/font');FontAttributor.whitelist = [ 'sofia', 'slabo', 'roboto', 'inconsolata', 'ubuntu',];Quill.register(FontAttributor, true);
Note you still need to add styling for these classes into your CSS files.
#### [](https://quilljs.com/docs/customization#customizing-blots)
Customizing Blots
Formats represented by Blots can also be customized. Here is how you would change the DOM Node used to represent bold formatting.
const Bold = Quill.import('formats/bold');
Bold.tagName = 'B'; // Quill uses
by default
Quill.register(Bold, true);
// Initialize as you would normally
const quill = new Quill('#editor', {
modules: {
toolbar: true
},
theme: 'snow'
});
const Delta = Quill.import('delta');
quill.setContents(
new Delta()
.insert('Rendered with !', { bold: true })
.insert('\\n')
);
Run Code
#### [](https://quilljs.com/docs/customization#extending-blots)
Extending Blots
You can also extend existing formats. Here is a quick ES6 implementation of a list item that does not permit formatting its contents. Code blocks are implemented in exactly this way.
const ListItem = Quill.import('formats/list/item');
class PlainListItem extends ListItem { formatAt(index, length, name, value) { if (name === 'list') { // Allow changing or removing list format super.formatAt(name, value); } // Otherwise ignore }}
Quill.register(PlainListItem, true);
// Initialize as you would normallyconst quill = new Quill('#editor', { modules: { toolbar: true, }, theme: 'snow',});
You can view a list of Blots and Attributors available by calling `console.log(Quill.imports);`. Direct modification of this object is not supported. Use [`Quill.register`](https://quilljs.com/docs/api#register)
instead.
A complete reference on Parchment, Blots and Attributors can be found on Parchment's own [README](https://github.com/quilljs/parchment/)
. For an in-depth walkthrough, take a look at [Cloning Medium with Parchment](https://quilljs.com/guides/cloning-medium-with-parchment)
, which starts with Quill understanding just plain text, to adding all of the formats [Medium](https://medium.com/)
supports. Most of the time, you will not have to build formats from scratch since most are already implemented in Quill, but it is still useful to understanding how Quill works at this deeper level.
* * *
An Open Source Project
----------------------
Quill is developed and maintained by [Slab](https://slab.com/)
. It is permissively licensed under BSD. Use it freely in personal or commercial projects!
[Star](https://github.com/slab/quill/ "Star Quill on GitHub")
[37,622](https://github.com/slab/quill/stargazers "Quill Stargazers")
---
# Building a Custom Module - Quill Rich Text Editor
Document Navigation
* [Quickstart](https://quilljs.com/docs/quickstart)
* [Why Quill](https://quilljs.com/docs/why-quill)
* [Installation](https://quilljs.com/docs/installation)
* [Upgrading to 2.0](https://quilljs.com/docs/upgrading-to-2-0)
* [Configuration](https://quilljs.com/docs/configuration)
* [Formats](https://quilljs.com/docs/formats)
* [API](https://quilljs.com/docs/api)
* [Content](https://quilljs.com/docs/api#content)
* [Formatting](https://quilljs.com/docs/api#formatting)
* [Selection](https://quilljs.com/docs/api#selection)
* [Editor](https://quilljs.com/docs/api#editor)
* [Events](https://quilljs.com/docs/api#events)
* [Model](https://quilljs.com/docs/api#model)
* [Extension](https://quilljs.com/docs/api#extension)
* [Delta](https://quilljs.com/docs/delta)
* [Modules](https://quilljs.com/docs/modules)
* [Toolbar](https://quilljs.com/docs/modules/toolbar)
* [Keyboard](https://quilljs.com/docs/modules/keyboard)
* [History](https://quilljs.com/docs/modules/history)
* [Clipboard](https://quilljs.com/docs/modules/clipboard)
* [Syntax](https://quilljs.com/docs/modules/syntax)
* [Customization](https://quilljs.com/docs/customization)
* [Themes](https://quilljs.com/docs/customization/themes)
* [Registries](https://quilljs.com/docs/customization/registries)
* [Guides](https://quilljs.com/docs/guides/designing-the-delta-format)
* [Designing the Delta Format](https://quilljs.com/docs/guides/designing-the-delta-format)
* [Building a Custom Module](https://quilljs.com/docs/guides/building-a-custom-module)
* [Cloning Medium with Parchment](https://quilljs.com/docs/guides/cloning-medium-with-parchment)
DocumentationBuilding a Custom Module
[Edit page on GitHub ↗](https://github.com/slab/quill/tree/main/packages/website/content/docs/guides/building-a-custom-module.mdx "Edit on GitHub")
Building a Custom Module
========================
Quill's core strength as an editor is its rich API and powerful customization capabilities. As you implement functionality on top of Quill's API, it may be convenient to organize this as a module. For the purpose of this guide, we will walk through one way to build a word counter module, a commonly found feature in many word processors.
Note
Internally modules are how much of Quill's functionality is organized. You can overwrite these default [modules](https://quilljs.com/docs/modules)
by implementing your own and registering it with the same name.
### [](https://quilljs.com/docs/guides/building-a-custom-module#counting-words)
Counting Words
At its core a word counter simply counts the number of words in the editor and displays this value in some UI. Thus we need to:
1. Listen for text changes in Quill.
2. Count the number of words.
3. Display this value.
Let's jump straight in with a complete example!
index.htmlindex.cssindex.js
0
Hide Result
That's all it takes to add a custom module to Quill! A function can be [registered](https://quilljs.com/docs/api#quillregistermodule/)
as a module and it will be passed the corresponding Quill editor object along with any options.
### [](https://quilljs.com/docs/guides/building-a-custom-module#using-options)
Using Options
Modules are passed an options object that can be used to fine tune the desired behavior. We can use this to accept a selector for the counter container instead of a hard-coded string. Let's also customize the counter to either count words or characters:
index.htmlindex.cssindex.js
0
Hide Result
### [](https://quilljs.com/docs/guides/building-a-custom-module#constructors)
Constructors
Since any function can be registered as a Quill module, we could have implemented our counter as an ES5 constructor or ES6 class. This allows us to access and utilize the module directly.
index.htmlindex.cssindex.js
0
Hide Result
### [](https://quilljs.com/docs/guides/building-a-custom-module#wrapping-it-all-up)
Wrapping It All Up
Now let's polish off the module in ES6 and fix a few pesky bugs. That's all there is to it!
index.htmlindex.cssindex.js
0
Hide Result
* * *
An Open Source Project
----------------------
Quill is developed and maintained by [Slab](https://slab.com/)
. It is permissively licensed under BSD. Use it freely in personal or commercial projects!
[Star](https://github.com/slab/quill/ "Star Quill on GitHub")
[37,622](https://github.com/slab/quill/stargazers "Quill Stargazers")
---
# Keyboard Module - Quill Rich Text Editor
Document Navigation
* [Quickstart](https://quilljs.com/docs/quickstart)
* [Why Quill](https://quilljs.com/docs/why-quill)
* [Installation](https://quilljs.com/docs/installation)
* [Upgrading to 2.0](https://quilljs.com/docs/upgrading-to-2-0)
* [Configuration](https://quilljs.com/docs/configuration)
* [Formats](https://quilljs.com/docs/formats)
* [API](https://quilljs.com/docs/api)
* [Content](https://quilljs.com/docs/api#content)
* [Formatting](https://quilljs.com/docs/api#formatting)
* [Selection](https://quilljs.com/docs/api#selection)
* [Editor](https://quilljs.com/docs/api#editor)
* [Events](https://quilljs.com/docs/api#events)
* [Model](https://quilljs.com/docs/api#model)
* [Extension](https://quilljs.com/docs/api#extension)
* [Delta](https://quilljs.com/docs/delta)
* [Modules](https://quilljs.com/docs/modules)
* [Toolbar](https://quilljs.com/docs/modules/toolbar)
* [Keyboard](https://quilljs.com/docs/modules/keyboard)
* [History](https://quilljs.com/docs/modules/history)
* [Clipboard](https://quilljs.com/docs/modules/clipboard)
* [Syntax](https://quilljs.com/docs/modules/syntax)
* [Customization](https://quilljs.com/docs/customization)
* [Themes](https://quilljs.com/docs/customization/themes)
* [Registries](https://quilljs.com/docs/customization/registries)
* [Guides](https://quilljs.com/docs/guides/designing-the-delta-format)
* [Designing the Delta Format](https://quilljs.com/docs/guides/designing-the-delta-format)
* [Building a Custom Module](https://quilljs.com/docs/guides/building-a-custom-module)
* [Cloning Medium with Parchment](https://quilljs.com/docs/guides/cloning-medium-with-parchment)
DocumentationKeyboard Module
[Edit page on GitHub ↗](https://github.com/slab/quill/tree/main/packages/website/content/docs/modules/keyboard.mdx "Edit on GitHub")
Keyboard Module
===============
The Keyboard module enables custom behavior for keyboard events in particular contexts. Quill uses this to bind formatting hotkeys and prevent undesirable browser side effects.
### [](https://quilljs.com/docs/modules/keyboard#key-bindings)
Key Bindings
Keyboard handlers are bound to a particular key and key modifiers. The `key` is the JavaScript event key code, but string shorthands are allowed for alphanumeric keys and some common keys.
Key modifiers include: `metaKey`, `ctrlKey`, `shiftKey` and `altKey`. In addition, `shortKey` is a platform specific modifier equivalent to `metaKey` on a Mac and `ctrlKey` on Linux and Windows.
Handlers will be called with `this` bound to the keyboard instance and be passed the current selection range.
quill.keyboard.addBinding({ key: 'b', shortKey: true}, function(range, context) { this.quill.formatText(range, 'bold', true);});
// addBinding may also be called with one parameter,// in the same form as in initializationquill.keyboard.addBinding({ key: 'b', shortKey: true, handler: function(range, context) {
}});
If a modifier key is `false`, it is assumed to mean that modifier is not active. You may also pass `null` to mean any value for the modifier.
// Only b with no modifier will triggerquill.keyboard.addBinding({ key: 'b' }, handler);
// Only shift+b will triggerquill.keyboard.addBinding({ key: ['b', 'B'], shiftKey: true }, handler);
// Either b or shift+b will triggerquill.keyboard.addBinding({ key: ['b', 'B'], shiftKey: null }, handler);
Multiple handlers may be bound to the same key and modifier combination. Handlers will be called synchronously, in the order they were bound. By default, a handler stops propagating to the next handler, unless it explicitly returns `true`.
quill.keyboard.addBinding({ key: 'Tab' }, function(range) { // I will normally prevent handlers of the tab key // Return true to let later handlers be called return true;});
Note: Since Quill's default handlers are added at initialization, the only way to prevent them is to add yours in the [configuration](https://quilljs.com/docs/modules/keyboard#configuration)
.
### [](https://quilljs.com/docs/modules/keyboard#context)
Context
Contexts enable further specification for handlers to be called only in particular scenarios. Regardless if context is specified, a context object is provided as a second parameter for all handlers.
// If the user hits backspace at the beginning of list or blockquote,// remove the format instead delete any textquill.keyboard.addBinding({ key: 'Backspace' }, { collapsed: true, format: ['blockquote', 'list'], offset: 0}, function(range, context) { if (context.format.list) { this.quill.format('list', false); } else { this.quill.format('blockquote', false); }});
#### [](https://quilljs.com/docs/modules/keyboard#collapsed)
collapsed
If `true`, handler is called only if the user's selection is collapsed, i.e. in cursor form. If `false`, the users's selection must be non-zero length, such as when the user has highlighted text.
#### [](https://quilljs.com/docs/modules/keyboard#empty)
empty
If `true`, called only if user's selection is on an empty line, `false` for a non-empty line. Note setting empty to be true implies collapsed is also true and offset is 0—otherwise the user's selection would not be on an empty line.
// If the user hits enter on an empty list, remove the list insteadquill.keyboard.addBinding({ key: 'Enter' }, { empty: true, // implies collapsed: true and offset: 0 format: ['list']}, function(range, context) { this.quill.format('list', false);});
#### [](https://quilljs.com/docs/modules/keyboard#format)
format
When an Array, handler will be called if _any_ of the specified formats are active. When an Object, _all_ specified formats conditions must be met. In either case, the format property of the context parameter will be an Object of all current active formats, the same returned by `quill.getFormat()`.
const context = { format: { list: true, // must be on a list, but can be any value script: 'super', // must be exactly 'super', 'sub' will not suffice link: false // cannot be in any link }};
#### [](https://quilljs.com/docs/modules/keyboard#offset)
offset
Handler will be only called when the user's selection starts `offset` characters from the beginning of the line. Note this is before printable keys have been applied. This is useful in combination with other context specifications.
#### [](https://quilljs.com/docs/modules/keyboard#prefix)
prefix
Regex that must match the text immediately preceding the user's selection's start position. The text will not match cross format boundaries. The supplied `context.prefix` value will be the entire immediately preceding text, not just the regex match.
// When the user types space...quill.keyboard.addBinding({ key: ' ' }, { collapsed: true, format: { list: false }, // ...on a line that's not already a list prefix: /^-$/, // ...following a '-' character offset: 1, // ...at the 1st position of the line, // otherwise handler would trigger if the user // typed hyphen+space mid sentence}, function(range, context) { // the space character is consumed by this handler // so we only need to delete the hyphen this.quill.deleteText(range.index - 1, 1); // apply bullet formatting to the line this.quill.formatLine(range.index, 1, 'list', 'bullet'); // restore selection this.quill.setSelection(range.index - 1);
// console.log(context.prefix) would print '-'});
#### [](https://quilljs.com/docs/modules/keyboard#suffix)
suffix
The same as [`prefix`](https://quilljs.com/docs/modules/keyboard#prefix)
except matching text immediately following the user's selection's end position.
### [](https://quilljs.com/docs/modules/keyboard#configuration)
Configuration
By default, Quill comes with several useful key bindings, for example indenting lists with tabs. You can add your own upon initialization.
Some bindings are essential to preventing dangerous browser defaults, such as the enter and backspace keys. You cannot remove these bindings to revert to native browser behaviors. However since bindings specified in the configuration will run before Quill's defaults, you can handle special cases and propagate to Quill's otherwise.
Adding a binding with `quill.keyboard.addBinding` will not run before Quill's because the defaults bindings will have been added by that point.
Each binding config must contain `key` and `handler` options, and may optionally include any of the `context` options.
const bindings = { // This will overwrite the default binding also named 'tab' tab: { key: 9, handler: function() { // Handle tab } },
// There is no default binding named 'custom' // so this will be added without overwriting anything custom: { key: ['b', 'B'], shiftKey: true, handler: function(range, context) { // Handle shift+b } },
list: { key: 'Backspace', format: ['list'], handler: function(range, context) { if (context.offset === 0) { // When backspace on the first character of a list, // remove the list instead this.quill.format('list', false, Quill.sources.USER); } else { // Otherwise propogate to Quill's default return true; } } }};
const quill = new Quill('#editor', { modules: { keyboard: { bindings: bindings } }});
### [](https://quilljs.com/docs/modules/keyboard#performance)
Performance
Like DOM events, Quill key bindings are blocking calls on every match, so it is a bad idea to have a very expensive handler for a very common key binding. Apply the same performance best practices as you would when attaching to common blocking DOM events, like `scroll` or `mousemove`.
* * *
An Open Source Project
----------------------
Quill is developed and maintained by [Slab](https://slab.com/)
. It is permissively licensed under BSD. Use it freely in personal or commercial projects!
[Star](https://github.com/slab/quill/ "Star Quill on GitHub")
[37,622](https://github.com/slab/quill/stargazers "Quill Stargazers")
---
# Designing the Delta Format - Quill Rich Text Editor
Document Navigation
* [Quickstart](https://quilljs.com/docs/quickstart)
* [Why Quill](https://quilljs.com/docs/why-quill)
* [Installation](https://quilljs.com/docs/installation)
* [Upgrading to 2.0](https://quilljs.com/docs/upgrading-to-2-0)
* [Configuration](https://quilljs.com/docs/configuration)
* [Formats](https://quilljs.com/docs/formats)
* [API](https://quilljs.com/docs/api)
* [Content](https://quilljs.com/docs/api#content)
* [Formatting](https://quilljs.com/docs/api#formatting)
* [Selection](https://quilljs.com/docs/api#selection)
* [Editor](https://quilljs.com/docs/api#editor)
* [Events](https://quilljs.com/docs/api#events)
* [Model](https://quilljs.com/docs/api#model)
* [Extension](https://quilljs.com/docs/api#extension)
* [Delta](https://quilljs.com/docs/delta)
* [Modules](https://quilljs.com/docs/modules)
* [Toolbar](https://quilljs.com/docs/modules/toolbar)
* [Keyboard](https://quilljs.com/docs/modules/keyboard)
* [History](https://quilljs.com/docs/modules/history)
* [Clipboard](https://quilljs.com/docs/modules/clipboard)
* [Syntax](https://quilljs.com/docs/modules/syntax)
* [Customization](https://quilljs.com/docs/customization)
* [Themes](https://quilljs.com/docs/customization/themes)
* [Registries](https://quilljs.com/docs/customization/registries)
* [Guides](https://quilljs.com/docs/guides/designing-the-delta-format)
* [Designing the Delta Format](https://quilljs.com/docs/guides/designing-the-delta-format)
* [Building a Custom Module](https://quilljs.com/docs/guides/building-a-custom-module)
* [Cloning Medium with Parchment](https://quilljs.com/docs/guides/cloning-medium-with-parchment)
DocumentationDesigning the Delta Format
[Edit page on GitHub ↗](https://github.com/slab/quill/tree/main/packages/website/content/docs/guides/designing-the-delta-format.mdx "Edit on GitHub")
Designing the Delta Format
==========================
Rich text editors lack a specification to express its own contents. Until recently, most rich text editors did not even know what was in their own edit areas. These editors just pass the user HTML, along with the burden of parsing and interpretting this. At any given time, this interpretation will differ from those of major browser vendors, leading to different editing experiences for users.
Quill is the first rich text editor to actually understand its own contents. Key to this is Deltas, the specification describing rich text. Deltas are designed to be easy to understand and use. We will walk through some of the thinking behind Deltas, to shed light on _why_ things are the way they are.
If you are looking for a reference on _what_ Deltas are, the [Delta documentation](https://quilljs.com/docs/delta)
is a more concise resource.
[](https://quilljs.com/docs/guides/designing-the-delta-format#plain-text)
Plain Text
------------------------------------------------------------------------------------
Let's start at the basics with just plain text. There already is a ubiquitous format to store plain text: the string. Now if we want to build upon this and describe formatted text, such as when a range is bold, we need to add additional information.
Arrays are the only other ordered data type available, so we use an array of objects. This also allows us to leverage JSON for compatibility with a breadth of tools.
const content = [ { text: 'Hello' }, { text: 'World', bold: true }];
We can add italics, underline, and other formats to the main object if we want to; but it is cleaner to separate `text` from all of this so we organize formatting under one field, which we will name `attributes`.
const content = [ { text: 'Hello' }, { text: 'World', attributes: { bold: true } }];
### [](https://quilljs.com/docs/guides/designing-the-delta-format#compact)
Compact
Even with our simple Delta format so far, it is unpredictable since the above "Hello World" example can be represented differently, so we cannot predict which will be generated:
const content = [ { text: 'Hel' }, { text: 'lo' }, { text: 'World', attributes: { bold: true } }];
To solve this, we add the constraint that Deltas must be compact. With this constraint, the above representation is not a valid Delta, since it can be represented more compactly by the previous example, where "Hel" and "lo" were not separate. Similarly we cannot have `{ bold: false, italic: true, underline: null }`, because `{ italic: true }` is more compact.
### [](https://quilljs.com/docs/guides/designing-the-delta-format#canonical)
Canonical
We have not assigned any meaning to `bold`, just that it describes some formatting for text. We could very well have used different names, such as `weighted` or `strong`, or used a different range of possible values, such as a numerical or descriptive range of weights. An example can be found in CSS, where most of these ambiguities are at play. If we saw bolded text on a page, we cannot predict if its rule set is `font-weight: bold` or `font-weight: 700`. This makes the task of parsing CSS to discern its meaning, much more complex.
We do not define the set of possible attributes, nor their meanings, but we do add an additional constraint that Deltas must be canonical. If two Deltas are equal, the content they represent must be equal, and there cannot be two unequal Deltas that represent the same content. Programmatically, this allows you to simply deep compare two Deltas to determine if the content they represent is equal.
So if we had the following, the only conclusion we can draw is `a` is different from `b`, but not what `a` or `b` means.
const content = [{ text: "Mystery", attributes: { a: true, b: true }}];
It is up to the implementer to pick appropriate names:
const content = [{ text: "Mystery", attributes: { italic: true, bold: true }}];
This canonicalization applies to both keys and values, `text` and `attributes`. For example, Quill by default:
* Uses six character hex values to represent colors and not RGB
* There is only one way to represent a newline which is with `\n`, not `\r` or `\r\n`
* `text: "Hello World"` unambiguously means there are precisely two spaces between "Hello" and "World"
Some of these choices may be customized by the user, but the canonical constraint in Deltas dictate that the choice must be unique.
This unambiguous predictability makes Deltas easier to work with, both because you have fewer cases to handle and because there are no surprises in what a corresponding Delta will look like. Long term, this makes applications using Deltas easier to understand and maintain.
[](https://quilljs.com/docs/guides/designing-the-delta-format#line-formatting)
Line Formatting
----------------------------------------------------------------------------------------------
Line formats affect the contents of an entire line, so they present an interesting challenge for our compact and canonical constraints. A seemingly reasonable way to represent centered text would be the following:
const content = [ { text: "Hello", attributes: { align: "center" } }, { text: "\nWorld" }];
But what if the user deletes the newline character? If we just naively get rid of the newline character, the Delta would now look like this:
const content = [ { text: "Hello", attributes: { align: "center" } }, { text: "World" }];
Is this line still centered? If the answer is no, then our representation is not compact, since we do not need the attribute object and can combine the two strings:
const content = [ { text: "HelloWorld" }];
But if the answer is yes, then we violate the canonical constraint since any permutation of characters having an align attribute would represent the same content.
So we cannot just naively get rid of the newline character. We also have to either get rid of line attributes, or expand them to fill all characters on the line.
What if we removed the newline from the following?
const content = [ { text: "Hello", attributes: { align: "center" } }, { text: "\n" }, { text: "World", attributes: { align: "right" } }];
It is not clear if our resulting line is aligned center or right. We could delete both or have some ordering rule to favor one over the other, but our Delta is becoming more complex and harder to work with on this path.
This problem begs for atomicity, and we find this in the _newline_ character itself. But we have an off by one problem in that if we have _n_ lines, we only have _n-1_ newline characters.
To solve this, Quill "adds" a newline to all documents and always ends Deltas with "\\n".
// Hello World on two linesconst content = [ { text: "Hello" }, { text: "\n", attributes: { align: "center" } }, { text: "World" }, { text: "\n", attributes: { align: "right" } } // Deltas must end with newline];
[](https://quilljs.com/docs/guides/designing-the-delta-format#embedded-content)
Embedded Content
------------------------------------------------------------------------------------------------
We want to add embedded content like images or video. Strings were natural to use for text but we have a lot more options for embeds. Since there are different types of embeds, our choice just needs to include this type information, and then the actual content. There are many reasonable options here but we will use an object whose only key is the embed type and the value is the content representation, which may have any type or value.
const img = { image: { url: 'https://quilljs.com/logo.png' }};
const f = { formula: 'e=mc^2'};
Similar to text, images might have some defining characteristics, and some transient ones. We used `attributes` for text content and can use the same `attributes` field for images. But because of this, we can keep the general structure we have been using, but should rename our `text` key into something more general. For reasons we will explore later, we will choose the name `insert`. Putting this all together we have:
const content = [{ insert: 'Hello'}, { insert: 'World', attributes: { bold: true }}, { insert: { image: 'https://exclamation.com/mark.png' }, attributes: { width: '100' }}];
[](https://quilljs.com/docs/guides/designing-the-delta-format#describing-changes)
Describing Changes
----------------------------------------------------------------------------------------------------
As the name Delta implies, our format can describe changes to documents, as well as documents themselves. In fact we can think of documents as the changes we would make to the empty document, to get to the one we are describing. As you might have already guessed, using Deltas to also describe changes is why we renamed `text` to `insert` earlier. We call each element in our Delta array an Operation.
#### [](https://quilljs.com/docs/guides/designing-the-delta-format#delete)
Delete
To describe deleting text, we need to know where and how many characters to delete. To delete embeds, there needs not be any special treatment, other than to understand the length of an embed. If it is anything other than one, we would then need to specify what happens when only part of an embed is deleted. There is currently no such specification, so regardless of how many pixels make up an image, how many minutes long a video is, or how many slides are in a deck; embeds are all of length **one**.
One reasonable way to describe a deletion is to explicitly store its index and length.
const delta = [{ delete: { index: 4, length: 1 }}, { delete: { index: 12, length: 3 }}];
We would have to order the deletions based on indexes, and ensure no ranges overlap, otherwise our canonical constraint would be violated. There are a couple other shortcomings to this index and length approach, but they are easier to appreciate after describing format changes.
#### [](https://quilljs.com/docs/guides/designing-the-delta-format#insert)
Insert
Now that Deltas may be describing changes to a non-empty document, `{ insert: "Hello" }` is insufficient, because we do not know where "Hello" should be inserted. We can solve this by also adding an index, similar to `delete`.
#### [](https://quilljs.com/docs/guides/designing-the-delta-format#format)
Format
Similar to deletes, we need to specify the range of text to format, along with the format change itself. Formatting exists in the `attributes` object, so a simple solution is to provide an additional `attributes` object to merge with the existing one. This merge is shallow to keep things simple. We have not found a use case that is compelling enough to require a deep merge and warrants the added complexity.
const delta = [{ format: { index: 4, length: 1 }, attributes: { bold: true }}];
The special case is when we want to remove formatting. We will use `null` for this purpose, so `{ bold: null }` would mean remove the bold format. We could have specified any falsy value, but there may be legitimate use cases for an attribute value to be `0` or the empty string.
**Note:** We now have to be careful with indexes at the application layer. As mentioned earlier, Deltas do not ascribe any inherent meaning to any the `attributes`' key-value pairs, nor any embed types or values. Deltas do not know an image does not have duration, text does not have alternative texts, and videos cannot be bolded. The following is a _legal_ Delta that might have been the result of applying other _legal_ Deltas, by an application not being careful of format ranges.
const delta = [{ insert: { image: "https://imgur.com/" }, attributes: { duration: 600 }}, { insert: "Hello", attributes: { alt: "Funny cat photo" }}, { insert: { video: "https://youtube.com/" }, attributes: { bold: true }}];
#### [](https://quilljs.com/docs/guides/designing-the-delta-format#pitfalls)
Pitfalls
First, we should be clear that an index must refer to its position in the document **before** any Operations are applied. Otherwise, a later Operation may delete a previous insert, unformat a previous format, etc., which would violate compactness.
Operations must also be strictly ordered to satisfy our canonical constraint. Ordering by index, then length, and then type is one valid way this can be accomplished.
As stated earlier, delete ranges cannot overlap. The case against overlapping format ranges is less brief, but it turns out we do not want overlapping formats either.
The number of reasons a Delta might be invalid is piling up. A better format would simply not allow such cases to be expressed at all.
#### [](https://quilljs.com/docs/guides/designing-the-delta-format#retain)
Retain
If we step back from our compactness formalities for a moment, we can describe a much simpler format to express inserting, deleting, and formatting:
* A Delta would have Operations that are at least as long as the document being modified.
* Each Operation would describe what happens to the character at that index.
* Optional insert Operations may make the Delta longer than the document it describes.
This necessitates the creation of a new Operation, that will simply mean "keep this character as is". We call this a `retain`.
// Starting with "HelloWorld",// bold "Hello", and insert a space right after itconst change = [ { format: true, attributes: { bold: true } }, // H { format: true, attributes: { bold: true } }, // e { format: true, attributes: { bold: true } }, // l { format: true, attributes: { bold: true } }, // l { format: true, attributes: { bold: true } }, // o { insert: ' ' }, { retain: true }, // W { retain: true }, // o { retain: true }, // r { retain: true }, // l { retain: true } // d]
Since every character is described, explicit indexes and lengths are no longer necessary. This makes overlapping ranges and out-of-order indexes impossible to express.
Therefore, we can make the easy optimization to merge adjacent equal Operations, re-introducing _length_. If the last Operation is a `retain` we can simply drop it, for it simply instructs to "do nothing to the rest of the document".
const change = [ { format: 5, attributes: { bold: true } } { insert: ' ' }]
Furthermore, you might notice that a `retain` is in some ways just a special case of `format`. For instance, there is no practical difference between `{ format: 1, attributes: {} }` and `{ retain: 1 }`. Compacting would drop the empty `attributes` object leaving us with just `{ format: 1 }`, creating a canonicalization conflict. Thus, in our example we will simply combine `format` and `retain`, and keep the name `retain`.
const change = [ { retain: 5, attributes: { bold: true } }, { insert: ' ' }]
We now have a Delta that is very close to the current standard format.
#### [](https://quilljs.com/docs/guides/designing-the-delta-format#ops)
Ops
Right now we have an easy to use JSON Array that describes rich text. This is great at the storage and transport layers, but applications could benefit from more functionality. We can add this by implementing Deltas as a class, that can be easily initialized from or exported to JSON, and then providing it with relevant methods.
At the time of Delta's inception, it was not possible to sub-class an Array. For this reason Deltas are expressed as Objects, with a single property `ops` that stores an array of Operations like the ones we have been discussing.
const delta = { ops: [{ insert: 'Hello' }, { insert: 'World', attributes: { bold: true } }, { insert: { image: 'https://exclamation.com/mark.png' }, attributes: { width: '100' } }]};
Finally, we arrive at the [Delta format](https://quilljs.com/docs/delta)
, as it exists today.
* * *
An Open Source Project
----------------------
Quill is developed and maintained by [Slab](https://slab.com/)
. It is permissively licensed under BSD. Use it freely in personal or commercial projects!
[Star](https://github.com/slab/quill/ "Star Quill on GitHub")
[37,622](https://github.com/slab/quill/stargazers "Quill Stargazers")
---
# Cloning Medium with Parchment - Quill Rich Text Editor
Document Navigation
* [Quickstart](https://quilljs.com/docs/quickstart)
* [Why Quill](https://quilljs.com/docs/why-quill)
* [Installation](https://quilljs.com/docs/installation)
* [Upgrading to 2.0](https://quilljs.com/docs/upgrading-to-2-0)
* [Configuration](https://quilljs.com/docs/configuration)
* [Formats](https://quilljs.com/docs/formats)
* [API](https://quilljs.com/docs/api)
* [Content](https://quilljs.com/docs/api#content)
* [Formatting](https://quilljs.com/docs/api#formatting)
* [Selection](https://quilljs.com/docs/api#selection)
* [Editor](https://quilljs.com/docs/api#editor)
* [Events](https://quilljs.com/docs/api#events)
* [Model](https://quilljs.com/docs/api#model)
* [Extension](https://quilljs.com/docs/api#extension)
* [Delta](https://quilljs.com/docs/delta)
* [Modules](https://quilljs.com/docs/modules)
* [Toolbar](https://quilljs.com/docs/modules/toolbar)
* [Keyboard](https://quilljs.com/docs/modules/keyboard)
* [History](https://quilljs.com/docs/modules/history)
* [Clipboard](https://quilljs.com/docs/modules/clipboard)
* [Syntax](https://quilljs.com/docs/modules/syntax)
* [Customization](https://quilljs.com/docs/customization)
* [Themes](https://quilljs.com/docs/customization/themes)
* [Registries](https://quilljs.com/docs/customization/registries)
* [Guides](https://quilljs.com/docs/guides/designing-the-delta-format)
* [Designing the Delta Format](https://quilljs.com/docs/guides/designing-the-delta-format)
* [Building a Custom Module](https://quilljs.com/docs/guides/building-a-custom-module)
* [Cloning Medium with Parchment](https://quilljs.com/docs/guides/cloning-medium-with-parchment)
DocumentationCloning Medium with Parchment
[Edit page on GitHub ↗](https://github.com/slab/quill/tree/main/packages/website/content/docs/guides/cloning-medium-with-parchment.mdx "Edit on GitHub")
Cloning Medium with Parchment
=============================
To provide a consistent editing experience, you need both consistent data and predictable behaviors. The DOM unfortunately lacks both of these. The solution for modern editors is to maintain their own document model to represent their contents. [Parchment](https://github.com/quilljs/parchment/)
is that solution for Quill. It is organized in its own codebase with its own API layer. Through Parchment you can customize the content and formats Quill recognizes, or add entirely new ones.
In this guide, we will use the building blocks provided by Parchment and Quill to replicate the editor on Medium. We will start with the bare bones of Quill, without any themes, extraneous modules, or formats. At this basic level, Quill only understands plain text. But by the end of this guide, links, videos, and even tweets will be understood.
[](https://quilljs.com/docs/guides/cloning-medium-with-parchment#groundwork)
Groundwork
---------------------------------------------------------------------------------------
Let's start without even using Quill, with just a textarea and button, hooked up to a dummy event listener. We'll use jQuery for convenience throughout this guide, but neither Quill nor Parchment depends on this. We'll also add some basic styling, with the help of [Google Fonts](https://fonts.google.com/)
and [Font Awesome](https://fontawesome.io/)
. None of this has anything to do with Quill or Parchment, so we'll move through quickly.
Fileindex.htmlFileindex.jsFilestyles.css
1
2
Hide Result
[](https://quilljs.com/docs/guides/cloning-medium-with-parchment#adding-quill-core)
Adding Quill Core
-----------------------------------------------------------------------------------------------------
Next, we'll replace the textarea with Quill core, absent of themes, formats and extraneous modules. Open up your developer console to inspect the demo while you type into the editor. You can see the basic building blocks of a Parchment document at work.
Fileindex.htmlFileindex.jsFilestyles.css
1
2
Tell your story...
Hide Result
Like the DOM, a Parchment document is a tree. Its nodes, called Blots, are an abstraction over DOM Nodes. A few blots are already defined for us: Scroll, Block, Inline, Text and Break. As you type, a Text blot is synchronized with the corresponding DOM Text node; enters are handled by creating a new Block blot. In Parchment, Blots that can have children must have at least one child, so empty Blocks are filled with a Break blot. This makes handling leaves simple and predictable. All this is organized under a root Scroll blot.
You cannot observe an Inline blot by just typing at this point since it does not contribute meaningful structure or formatting to the document. A valid Quill document must be canonical and compact. There is only one valid DOM tree that can represent a given document, and that DOM tree contains the minimal number of nodes.
Since `Text
` and `Text
` represent the same content, the former is invalid and it is part of Quill's optimization process to unwrap the ``. Similarly, once we add formatting, `Te st
` and `Test
` are also invalid, as they are not the most compact representation.
Because of these constraints, **Quill cannot support arbitrary DOM trees and HTML changes**. But as we will see, the consistency and predicability this structure provides enables us to easily build rich editing experiences.
[](https://quilljs.com/docs/guides/cloning-medium-with-parchment#basic-formatting)
Basic Formatting
---------------------------------------------------------------------------------------------------
We mentioned earlier that an Inline does not contribute formatting. This is the exception, rather than the rule, made for the base Inline class. The base Block blot works the same way for block level elements.
To implement bold and italics, we need only to inherit from Inline, set the `blotName` and `tagName`, and register it with Quill. For a compelete reference of the signatures of inherited and static methods and variables, take a look at [Parchment](https://github.com/quilljs/parchment/)
.
const Inline = Quill.import('blots/inline');
class BoldBlot extends Inline { static blotName = 'bold'; static tagName = 'strong';}
class ItalicBlot extends Inline { static blotName = 'italic'; static tagName = 'em';}
Quill.register(BoldBlot);Quill.register(ItalicBlot);
We follow Medium's example here in using `strong` and `em` tags but you could just as well use `b` and `i` tags. The name of the blot will be used as the name of the format by Quill. By registering our blots, we can now use Quill's full API on our new formats:
Quill.register(BoldBlot);Quill.register(ItalicBlot);
const quill = new Quill('#editor');
quill.insertText(0, 'Test', { bold: true });quill.formatText(0, 4, 'italic', true);// If we named our italic blot "myitalic", we would call// quill.formatText(0, 4, 'myitalic', true);
Let's get rid of our dummy button handler and hook up the bold and italic buttons to Quill's [`format()`](https://quilljs.com/docs/api#format)
. We will hardcode `true` to always add formatting for simplicity. In your application, you can use [`getFormat()`](https://quilljs.com/docs/api#getformat)
to retrieve the current formatting over a arbitrary range to decide whether to add or remove a format. The [Toolbar](https://quilljs.com/docs/modules/toolbar)
module implements this for Quill, and we will not reimplement it here.
Open your developer console and try out Quill's [APIs](https://quilljs.com/docs/api)
on your new bold and italic formats! Make sure to set the context to the correct CodePen iframe to be able to access the `quill` variable in the demo.
Directoryformats
FileboldBlot.jsFileitalicBlot.js
Fileindex.htmlFileindex.jsFilestyles.css
import './formats/boldBlot.js';
import './formats/italicBlot.js';
const onClick = (selector, callback) \=> {
document.querySelector(selector).addEventListener('click', callback);
};
onClick('#bold-button', () \=> {
quill.format('bold', true);
});
onClick('#italic-button', () \=> {
quill.format('italic', true);
});
const quill = new Quill('#editor');
Hide Result
Note that if you apply both bold and italic to some text, regardless of what order you do so, Quill wraps the `` tag outside of the `` tag, in a consistent order.
[](https://quilljs.com/docs/guides/cloning-medium-with-parchment#links)
Links
-----------------------------------------------------------------------------
Links are slightly more complicated, since we need more than a boolean to store the link url. This affects our Link blot in two ways: creation and format retrieval. We will represent the url as a string value, but we could easily do so in other ways, such as an object with a url key, allowing for other key/value pairs to be set and define a link. We will demonstrate this later with [images](https://quilljs.com/docs/guides/cloning-medium-with-parchment#images)
.
class LinkBlot extends Inline { static blotName = 'link'; static tagName = 'a';
static create(value) { const node = super.create(); // Sanitize url value if desired node.setAttribute('href', value); // Okay to set other non-format related attributes // These are invisible to Parchment so must be static node.setAttribute('target', '_blank'); return node; }
static formats(node) { // We will only be called with a node already // determined to be a Link blot, so we do // not need to check ourselves return node.getAttribute('href'); }}
Quill.register(LinkBlot);
Now we can hook our link button up to a fancy `prompt`, again to keep things simple, before passing to Quill's `format()`.
Directoryformats
FileboldBlot.jsFileitalicBlot.jsFilelinkBlot.js
Fileindex.htmlFileindex.jsFilestyles.css
const Inline = Quill.import('blots/inline');
class LinkBlot extends Inline {
static blotName = 'link';
static tagName = 'a';
static create(url) {
let node = super.create();
// Sanitize url if desired
node.setAttribute('href', url);
// Okay to set other non-format related attributes
node.setAttribute('target', '\_blank');
return node;
}
static formats(node) {
// We will only be called with a node already
// determined to be a Link blot, so we do
// not need to check ourselves
return node.getAttribute('href');
}
}
Quill.register(LinkBlot);
Hide Result
[](https://quilljs.com/docs/guides/cloning-medium-with-parchment#blockquote-and-headers)
Blockquote and Headers
---------------------------------------------------------------------------------------------------------------
Blockquotes are implemented the same way as Bold blots, except we will inherit from Block, the base block level Blot. While Inline blots can be nested, Block blots cannot. Instead of wrapping, Block blots replace one another when applied to the same text range.
const Block = Quill.import('blots/block');
class BlockquoteBlot extends Block { static blotName = 'blockquote'; static tagName = 'blockquote';}
Headers are implemented exactly the same way, with only one difference: it can be represented by more than one DOM element. The value of the format by default becomes the tagName, instead of just `true`. We can customize this by extending `formats()`, similar to how we did so for [links](https://quilljs.com/docs/guides/cloning-medium-with-parchment#links)
.
class HeaderBlot extends Block { static blotName = 'header'; // Medium only supports two header sizes, so we will only demonstrate two, // but we could easily just add more tags into this array static tagName = ['H1', 'H2'];
static formats(node) { return HeaderBlot.tagName.indexOf(node.tagName) + 1; }}
Let's hook these new blots up to their respective buttons and add some CSS for the `` tag.
Directoryformats
FileblockquoteBlot.jsFileboldBlot.jsFileheaderBlot.jsFileitalicBlot.jsFilelinkBlot.js
Fileindex.htmlFileindex.jsFilestyles.css
const Block = Quill.import('blots/block');
class BlockquoteBlot extends Block {
static blotName = 'blockquote';
static tagName = 'blockquote';
}
Quill.register(BlockquoteBlot);
Hide Result
Try setting some text to H1, and in your console, run `quill.getContents()`. You will see our custom static `formats()` function at work. Make sure to set the context to the correct CodePen iframe to be able to access the `quill` variable in the demo.
[](https://quilljs.com/docs/guides/cloning-medium-with-parchment#dividers)
Dividers
-----------------------------------------------------------------------------------
Now let's implement our first leaf Blot. While our previous Blot examples contribute formatting and implement `format()`, leaf Blots contribute content and implement `value()`. Leaf Blots can either be Text or Embed Blots, so our section divider will be an Embed. Once created, Embed Blots' value is immutable, requiring deletion and reinsertion to change the content at that location.
Our methodology is similar to before, except we inherit from a BlockEmbed. Embed also exists under `blots/embed`, but is meant for inline level blots. We want the block level implementation instead for dividers.
const BlockEmbed = Quill.import('blots/block/embed');
class DividerBlot extends BlockEmbed { static blotName = 'divider'; static tagName = 'hr';}
Our click handler calls [`insertEmbed()`](https://quilljs.com/docs/api#insertembed)
, which does not as conveniently determine, save, and restore the user selection for us like [`format()`](https://quilljs.com/docs/api#format)
does, so we have to do a little more work to preserve selection ourselves. In addition, when we try to insert a BlockEmbed in the middle of the Block, Quill splits the Block for us. To make this behavior more clear, we will explicitly split the block oursevles by inserting a newline before inserting the divider. Take a look at the Babel tab in the CodePen for specifics.
Directoryformats
FileblockquoteBlot.jsFileboldBlot.jsFiledividerBlot.jsFileheaderBlot.jsFileitalicBlot.jsFilelinkBlot.js
Fileindex.htmlFileindex.jsFilestyles.css
const BlockEmbed = Quill.import('blots/block/embed');
class DividerBlot extends BlockEmbed {
static blotName = 'divider';
static tagName = 'hr';
}
Quill.register(DividerBlot);
Hide Result
[](https://quilljs.com/docs/guides/cloning-medium-with-parchment#images)
Images
-------------------------------------------------------------------------------
Images can be added with what we learned building the [Link](https://quilljs.com/docs/guides/cloning-medium-with-parchment#links)
and [Divider](https://quilljs.com/docs/guides/cloning-medium-with-parchment#divider)
blots. We will use an object for the value to show how this is supported. Our button handler to insert images will use a static value, so we are not distracted by tooltip UI code irrelevant to [Parchment](https://github.com/quilljs/parchment/)
, the focus of this guide.
const BlockEmbed = Quill.import('blots/block/embed');
class ImageBlot extends BlockEmbed { static blotName = 'image'; static tagName = 'img';
static create(value) { const node = super.create(); node.setAttribute('alt', value.alt); node.setAttribute('src', value.url); return node; }
static value(node) { return { alt: node.getAttribute('alt'), url: node.getAttribute('src') }; }}
Directoryformats
FileblockquoteBlot.jsFileboldBlot.jsFiledividerBlot.jsFileheaderBlot.jsFileimageBlot.jsFileitalicBlot.jsFilelinkBlot.js
Fileindex.htmlFileindex.jsFilestyles.css
const BlockEmbed = Quill.import('blots/block/embed');
class ImageBlot extends BlockEmbed {
static blotName = 'image';
static tagName = 'img';
static create(value) {
let node = super.create();
node.setAttribute('alt', value.alt);
node.setAttribute('src', value.url);
return node;
}
static value(node) {
return {
alt: node.getAttribute('alt'),
url: node.getAttribute('src')
};
}
}
Quill.register(ImageBlot);
Hide Result
[](https://quilljs.com/docs/guides/cloning-medium-with-parchment#videos)
Videos
-------------------------------------------------------------------------------
We will implement videos in a similar way as we did [images](https://quilljs.com/docs/guides/cloning-medium-with-parchment#images)
. We could use the HTML5 `` tag but we cannot play YouTube videos this way, and since this is likely the more common and relevant use case, we will use an `