How to Use Quill Rich Text Editor With Livewire and Alpine.js

Configuring Quill.js rich text editor with vanilla JS is quite painless. You can have a look at their Quickstart code snippet to have it up and running in a few minutes.

However if you have a Laravel project and planning to configure Quill editor with Livewire and Alpine.js, then it require some more steps. Find the code below and I’ll try to explain the steps thereafter.

The first step is to load the dependencies. You can use the usual @stack & @push directives to load them.

The below goes in your Livewire component blade

<div class="my-4">
    <div x-data id="standalone-container" wire:ignore>
        <div id="editor-container" class="flex flex-1 flex-col h-80">
            <div
                x-ref="editor{{ $editor_type }}"
                x-init="quill = new Quill(
                        $refs.editor{{ $editor_type }}, {
                            modules: {
                                toolbar: [
                                    [{ header: [1, 2, false] }],
                                    ['bold', 'italic', 'underline'],
                                    [{ 'align': ['', 'right', 'center']}],
                                ],
                            },
                            placeholder: 'Enter your consent form contents here...',
                            theme: 'snow'
                    });
                    quill.on('text-change', function () {
                        @this.set('value', quill.getContents())
                    });
                "
            >
            </div>
        </div>
    </div>
</div>

The below code in your Livewire component controller

class QuillEditorLivewire extends Component
{
    public $value;
    public $editor_type;

    public function mount($value = ''){         //  Can we pass a Quill model instance to mount() so that it populates it with the persisted contents?
        $this->value = $value;
        
    }

    public function updatedValue($value) {
        $this->dispatch('quill-value-updated', val: $this->value);
    }

    public function render()
    {
        return view('livewire.components.quill-editor-livewire');
    }
}

The below goes in the controller of the components that might be wrapping this Quill component. An example could be a form that wants to implement Quill rich text editor instead of a textarea component.

#[On('quill-value-updated')]
public function quillValueUpdated($val)
{
    $this->document_content = $val;
}
...

$document = Document::create([
    'name' => $this->document_title,
    'content' => json_encode($this->document_content),
]);

Now, I’d try to explain what all is going on in here. If you’d look at the LW blade

First off, we use x-ref directive to assign a value to the element on which to mount the quill editor. If you’d notive, we are using a public property $editor_type. This is especially helpful if you are trying to load multiple instances of the editor on the same page.

x-ref="editor{{ $editor_type }}"

Next, x-init is used to hook into the element for mounting the editor.

x-init="quill = new Quill( $refs.editor{{ $editor_type }}, { theme: 'snow' });

The above is same as doing the below in vanilla JS.

<script>
  const quill = new Quill('#editor', { theme: 'snow' });
</script>

The above steps will mount the editor alright. But we have some additional work here. We want to update the public property of this LW component so that it is updated every time the contents of this component change. Quill has a very handy event for this - the text-change event.

quill.on('text-change', function () {
    @this.set('value', quill.getContents())
});

The text-change event provides a convenient callback function that we can use for updating the LW property. On the line @this.set('value', quill.getContents()) we are using a Quill method getContents() to populate the property value to the updated contents of the editor. You may want to explore another userful method getSemanticHTML() if you want to get the underlying HTML representation of the editor contents. A minor warning though: some CSS rules do not seem to work and you may have to manually apply them.

Next, in the LW controller, we leverage the updated lifecycle hook (refer LW v2 & LW v3) to dispatch a LW event to the parent component (which may have a form and would want to access the contents of the editor to later persist into the DB on form submit).

public function updatedValue($value) {
    $this->dispatch('quill-value-updated', val: $this->value);
}

As you can see in the below code that sits in the controller of the (parent) form, it listens for the event quill-value-updated and sets the value of a public property on the parent component.

#[On('quill-value-updated')]

The value of this public property is then persisted in the DB.

'content' => $this->document_content,

I hope the long text was worth your time. Thank you for reading through.