Using Quill to Build a WYSIWYG HTML Editor [Step-by-Step Tutorial]

ProfilePicture of Andres Canal
Andres Canal
Senior Full-stack Mobile Developer
Quill.js logo illustration

Remember how basic the Facebook status update used to be?

Screenshot of old Facebook Status
Status Box WYSIWYG Editor Quill

Now, that same space contains an array of sophisticated text styling options that let users fully express what’s on their minds. This evolution has been driven by rich-text editors like Quill, which allow visitors to fully customize their content: from embedding photos and videos to adding emoticons. In this article, I’ll share a step by step guide to installing and customizing this open-source tool, In this article, I’ll share a step-by-step guide to installing and customizing this open-source tool available for the front-end community.

Table Of Contents

I recently found myself in need of a simple, yet extendable, WYSIWYG HTML editor. A fully functional text editor is a complex piece of software, and writing one from scratch would not have been the best use of my time. I’m a big believer in not reinventing the wheel and making use of other people’s open-source code. But this mindset requires its own skillset: knowing how to quickly and efficiently evaluate options.

What Is Quill?

Quill.js is a JavaScript library that provides developers with a powerful, easy-to-use tool for building rich text editors on the web. With Quill, you can easily format text, add images and videos, and create custom, interactive content. The library is lightweight and easy to use, making it a popular choice for developers who want to add rich text editing capabilities to their web applications.

WYSIWYG Editor Options

The first thing I did was to Google ’WYSIWYG Editor’. As expected, the results were overwhelming. I found dozens of different options, in various states of maintenance and quality:

Alloy Editor, Aloha Editor, CKEditor, Content Tools, Etherpad, Froala, grande.js, jodit, Medium Editor, Medium.js, Mobiledoc Kit, Pell, Pen Editor, ProseMirror, Quill.js, Squire, Scribe, Slate JS, Substance, TinyMCE, Trix, Trumbowyg, and wysihtml.

Wow. Where to start?

As a front-end developer, you’re likely familiar with this situation. Every tech stack decision requires sorting through an ever-increasing number of frameworks, like the Tailwind CSS framework that divided the front-end community, preprocessors, JavaScript versions, and so on. Let’s take a slight detour from Quill.js to chat about evaluating software. Over time, I’ve developed a system of sorts for quickly doing this, which may be useful to you.

Spoiler Alert: I choose Quill.js at the end!

WYSIWYG Editor Evaluation Process

Graph of Quilljs downloads in 2023 compared to other editors

Getting Quill Up and Running

Now we have settled on Quill.js as our WYSIWYG editor of choice, let’s look at integrating it into our site, which is a pretty straightforward task, as it turns out.

Start by including Quill.js and Quill.js.snow.css to an HTML file:

1  <head>
2 <link href="" rel="stylesheet">
3 <script src=""></script>
4  </head>
5  <body></body>

Note: I’m using Quill’s most recent version (at the time of writing) via CDN for simplicity’s sake, but you can of course use npm or yarn instead. If you do use the CDN, check the releases page to see if there’s a newer version available and update the version numbers in the above code. They also seem to be (slowly) working on a 2.0 release, so keep an eye out for that in the future – it may add new functionality and require steps to upgrade existing code.

Now let’s add a <div> with the id attribute set to “editor” and a static height into the <body> tag. This is where our editor is going to live.

2    <div id="editor" style=height: 200px></div>

Now let’s add a script section to the bottom of the HTML file and initialize Quill. To do this, we have to tell Quill the id of the div where we want the editor to be added.

2 var quill = new Quill('#editor', {
3    theme: 'snow'
4  });

OK, we’re good to go! Open the file in your browser and you should see the default Quill.js editor view.

Screenshot of Quill.js editor view example

Let’s customize the toolbar and specify the styles we want it to display. To do this, we provide an array of our desired styles to Quill’s constructor. As shown here:

2  var toolbarOptions = [
3    ['bold', 'italic']
4  ]
5 var quill = new Quill('#editor', {
6    modules: {
7      toolbar: toolbarOptions
8    },
9    theme: 'snow'
10  });

We only want to only support ‘bold’ and ‘italic’ styling, so those are the options we provide to the constructor. If you want to see all the possible styles supported by quill’s toolbar you can go to the toolbar section in Quill’s documentation. Your toolbar should now look like this:

Screenshot of Quill.js editor view x2

If your editor will be used within a commenting engine, you’ll need a way to access the generated HTML. So let’s see how we can log the contents to the console. First, let’s add a button inside our <body> tag. This button is going to call a function and log the content of the editor.

2  <div id="editor style="height: 200px"></div>
3  <button onclick="logHtmlContent()">Log content as HTML</button>

Next, we will create a toolbar with the following options: font size, font color and background color. We will also add the logHtmlContent() function. This will be called when a user clicks the ‘Log content as HTML’ button we added below the WYSIWYG HTML editor.

2  var toolbarOptions = [
3    ['bold', 'italic'],
4    [{ 'size': ['small', false, 'large', 'huge'] }],
5    [{ 'color': [] }, { 'background': [] }]
6  ]
7  var quill = new Quill('#editor', {
8    modules: {
9      toolbar: toolbarOptions
10    },
11    theme: 'snow'
12  });
13  function logHtmlContent() {
14    console.log(quill.root.innerHTML);
15  }

Now reload the page and replace with some styled text:

Screenshot of Quill.js editor view x3

Now click the Log button and observe the output in your browser’s developer console:

1<p><span class="ql-size-large" style="background-color: rgb(0, 0, 0); color: rgb(255, 255, 255);">This</span> <span class="ql-size-small">is</span> a <span class="ql-size-huge" style="background-color: rgb(230, 0, 0); color: rgb(255, 255, 255);">text</span> <span class="ql-size-large" style="background-color: rgb(0, 138, 0); color: rgb(255, 255, 255);">message</span>.</p>

For some formats, Quill.js uses inline style on the HTML nodes. For others, it uses custom CSS classes. This is important because if you are saving these in a database, your CSS will have to support Quill’s CSS formats like ql-size-huge and ql-size-large.

Quill developed their own format, called Delta, to describe content and changes. It’s a human and machine-readable description of your text editor, but without the complexity of HTML. While covering it is beyond the scope of this post, you can dig deeper yourself by checking out their documentation.

Extending Quill to Support Custom Formats

Once you start familiarizing yourself with Quill.js, you may want to use formats that are not provided by default. For example, let’s say you want to add support for highlighted text using HTML’s <mark> tag.

Quill’s document model is called Parchment. Think of it as a custom DOM: little building blocks that, when put together in a certain order, build a document. So depending on the custom format we want to add, we will need to extend one of these ‘blocks’. In the parchment world, these building blocks are called “Blots”.

We’ll start by adding a ‘M’ button to the toolbar, that when clicked, surrounds the selected text with <mark></mark> tags.

1let Inline = Quill.import('blots/inline');
2class MarkBlot extends Inline { };
3MarkBlot.blotName = 'mark';
4MarkBlot.tagName = 'mark';

The above snippet is pretty easy to understand. We start by extending a class, setting a name for the blot and its associated HTML tag, then registering that blot so it can be added to the editor. You can add it at the top of your script tag before Quill is initialized. 

We now need to create a button to toggle the custom format. This time, to add the button to the toolbar, we will create our toolbar in plain HTML. We’ll use this method instead of the previous method where we sent Quill an array of formats. To accomplish this, let’s create a new <div> above the editor to hold our toolbar and add the buttons as shown below:

1<div id="toolbar">
2  <button class="ql-bold"></button>
3  <button class="ql-italic"></button>
4  <button class="ql-mark"><b>M<b></button>

Inside this <div> we need to add a button for each format we want to support. Using a ql-blot-name so Quill.js can figure out which blot to use for the selected text.

Once done, we need to initialize Quill. This time we need to provide Quill with the id of the toolbar we just created. This is the id of the div that contains the toolbar buttons:

1var quill = new Quill('#editor', {
2  modules: {
3    toolbar: {
4      container: '#toolbar'
5    }
6  },
7  theme: 'snow'

Refresh your browser and you should see your new functional Mark button. Try it out!

Screenshot of Quill.js editor view x4

Let’s raise the bar a bit here and create a truly custom style. “How about a text selection with a dotted border?” Good, I was hoping you would say that.

To do this we are going to use Attributors. In Parchment, an attributor is a lightweight way of representing formats. There are different types of attributors: here we are going to choose one that lets us apply a class to a selected element.

1const Parchment = Quill.import('parchment');
2var boxAttributor = new Parchment.Attributor.Class('box', 'line', {
3 scope: Parchment.Scope.INLINE,
4 whitelist: ['solid', 'double', 'dotted']

The above constructor receives three parameters: attributor name, key name, and a configuration object. We are creating a class attributor, which means that a class will be added to a node. In our example, the classes will be: “line-solid”, “line-double” and “line-dotted”.

Now we need to add the buttons that are going to apply the classes to our previously defined toolbar:

1<button class="ql-box" value="double">Dou</button>
2<button class="ql-box" value="solid">Line</button>
3<button class="ql-box" value="dotted">Dot</button>

Each new button has two attributes. The class tells Quill.js which attributor is going to be applied when clicked. The value defines which class the button is going to apply. These value options have to be previously defined when creating the attributor in the configuration object.

Before we test this, let’s define the new CSS classes that will apply the format to our selections:

2  .line-dotted {
3    border-style: dotted;
4  }
5  .line-solid {
6    border-style: solid;
7  }
8  .line-double {
9    border-style: double;
10  }

OK, let’s refresh the page and see our new styles in action:

Screenshot of Quill.js editor view x5

Those of you paying attention may be wondering “Why didn’t he just create a new blot called BoxBlot?” Great question. A blot creates a node, and we don’t want to create a node as they are used to group information and give semantic meaning. Here, we simply want to apply a format which is best done with an attribute.

While all three of our new buttons work, only one can be applied at any one time, so they should really be grouped together. Besides being just better UX practice, it will also serve to declutter the toolbar.

1<div id="toolbar">
2  <button class="ql-bold"></button>
3  <button class="ql-italic"></button>
4 <button class="ql-mark"><b>M</b></button>
5  <select class="ql-box">
6    <option selected>None</option>
7    <option value="double">Double</option>
8    <option value="solid">Solid</option>
9    <option value="dotted">Dotted</option>
10  </select>

Next, we have to add the following CSS rules (I came up with these rules after inspecting how Quill.js was building its fonts dropdown):

1.ql-snow .ql-picker.ql-box .ql-picker-label::before,
2.ql-snow .ql-picker.ql-box .ql-picker-item::before {
3    content: 'None'
6.ql-snow .ql-picker.ql-box .ql-picker-label[data-value="solid"]::before,
7.ql-snow .ql-picker.ql-box .ql-picker-item[data-value="solid"]::before {
8    content: "Solid";
11.ql-snow .ql-picker.ql-box .ql-picker-label[data-value="double"]::before,
12.ql-snow .ql-picker.ql-box .ql-picker-item[data-value="double"]::before {
13    content: "Double";
16.ql-snow .ql-picker.ql-box .ql-picker-label[data-value="dotted"]::before,
17.ql-snow .ql-picker.ql-box .ql-picker-item[data-value="dotted"]::before {
18    content: "Dotted";
21.ql-snow span.ql-picker.ql-box {
22    width: 8em;

We’re done! The end result should look like this:

Screenshot of Quill.js editor view x6


We started off the article with a rundown of the process I use to evaluate software with a focus on picking a WYSIWYG editor. As talented developers keep adding to the myriad of software options in the market, it becomes more and more important to be able to quickly and efficiently filter through them. The wrong choice can leave you in development hell: having to deal with buggy or unsupported products.

It’s that evaluation process that initially led me to the excellent Quill.js. The bulk of this article demonstrated not just how easy it is to set up, but how extensible and adaptable it is. If you want to provide your visitors with the rich text styling options they are becoming used to, I encourage you to try and work through the examples above. To do so, just head over to CodePen where I’ve stored all the code and functional examples of this project.

Originally published on Mar 30, 2020Last updated on Jan 8, 2023

Key Takeaways

What is Quill.js used for?

Quill.js is a JavaScript library that provides developers with a powerful, easy-to-use tool for building rich text editors on the web.

What is a WYSIWYG editor?

WYSIWYG, an acronym for What You See Is What You Get, is a system in which editing software allows content to be edited in a form that resembles its final appearance.

What is Quill in React?

Quill.js is a JavaScript library that provides developers with a powerful, easy-to-use tool for building rich text editors on the web. With Quill, you can easily format text, add images and videos, and create custom, interactive content. By using it along with React, developers can build multi-state sites that require frequent updates with ease.