This article covers adding a custom button to the Quill editor’s toolbar.
The short answer: put a button with a ql- prefixed class in your toolbar markup, then register a handler under the same name in handlers. The appearance lives in HTML, the behaviour in JavaScript.
This article shipped with an incorrect implementation in 2022. It has been rewritten in September 2026 with code that works on Quill 2.0.3.
Sponsored
Adding a custom button
Write the toolbar as HTML and hand the container to Quill. Name your button’s class ql-something and you can register a handler for something.
The toolbar markup:
<div id="toolbar">
<button class="ql-bold"></button>
<button class="ql-italic"></button>
<!-- the custom one -->
<button class="ql-insertStar">★</button>
</div>
<div id="editor"></div>
Names Quill already knows, such as ql-bold, get their icon filled in automatically if you leave the button empty. Quill does not know your custom name, so supply the content yourself.
Then pass the container in and register the handler:
const quill = new Quill('#editor', {
theme: 'snow',
modules: {
toolbar: {
container: '#toolbar',
handlers: {
// the key is the class name minus the ql- prefix
insertStar: function () {
const range = this.quill.getSelection(true);
this.quill.insertText(range.index, '★');
this.quill.setSelection(range.index + 1);
}
}
}
}
});
The handler key is the class name with ql- removed. ql-insertStar maps to insertStar. If they do not match, clicking the button does nothing at all.
Inside a handler, this is the toolbar module, so reach the editor through this.quill.
Registering a handler after initialisation
For an editor that is already running, use addHandler().
const toolbar = quill.getModule('toolbar');
toolbar.addHandler('insertStar', function () {
const range = this.quill.getSelection(true);
this.quill.insertText(range.index, '★');
});
The same method overrides a built-in button. A common use is replacing the image button with your own upload flow:
toolbar.addHandler('image', function () {
openImageUploader();
});
Styling the button
Target it by the class you gave it.
.ql-toolbar .ql-insertStar {
font-size: 18px;
color: #444;
}
.ql-toolbar .ql-insertStar:hover {
color: #06c;
}
Quill’s own buttons contain SVGs. To recolour those, target the stroke and the fill:
.ql-toolbar button:hover .ql-stroke {
stroke: #06c;
}
.ql-toolbar button:hover .ql-fill {
fill: #06c;
}
.ql-stroke is the outline and .ql-fill is the solid area. Different buttons use different ones, so if a colour will not change, try the other.
Sponsored
Using an image or icon font
The button’s contents are ordinary HTML, so an <svg> or an icon font both work.
<button class="ql-insertStar">
<i class="fa-solid fa-star"></i>
</button>
With Font Awesome, use the version 7 class names. The version 4 style, fa fa-star, renders nothing in current releases.
Common problems
If the button does nothing, check the class name against the handler key first.
▼Likely causes
| Symptom | Cause |
|---|---|
| Nothing happens on click | Class name and handler key do not match |
| Two toolbars appear | Passing an array as well as a container |
| The button renders blank | Quill does not know the name and you left it empty |
| The insertion point is wrong | getSelection(true) was called without true |
The true argument to getSelection() means “focus the editor if it is not focused”. Clicking a toolbar button moves focus away from the editor, so without it there is no selection to read and the call fails.
Summary
It comes down to one rule: the class name (ql-something) and the handler key (something) must match.
If Quill is not set up yet, start with adding the Quill rich text editor to Laravel. Quill 2.0 changed the CDN host, so older snippets will not load.
To extend the built-in dropdowns, see customising font-family and customising font-size.