| Install | |
|---|---|
composer require caiquebispo/quill-editor |
|
| Latest Version: | v1.3.4 |
| PHP: | ^8.1 |
Fixes included:
wire:ignore.self to scope DOM changesBasic usage:
<x-quill-editor :config="[
'height' => '240px',
'modules' => ['toolbar' => [['bold','italic','underline'], [{'header':1},{'header':2}], ['link']]],
]" />
Livewire component for rich text editing powered by Quill.js.
Supports editing with headings, bold, italic, lists, links, images, videos, alignment, colors, and more, fully integrated with Laravel and Livewire for seamless form and CMS workflows.
Core Features:
New Features (v1.2+):
composer require caiquebispo/quill-editor
In your main layout file (for example, resources/views/layouts/app.blade.php), include:
<head>
...
@stack('styles')
</head>
<body>
...
@stack('scripts')
</body>
For publishing the configuration and assets, run the following commands:
php artisan vendor:publish --provider="CaiqueBispo\QuillEditor\Provider\QuillEditorServiceProvider" --tag=config
php artisan vendor:publish --provider="CaiqueBispo\QuillEditor\Provider\QuillEditorServiceProvider" --tag=assets
This will create the config/quill.php file and copy the necessary assets to your public directory.
The published configuration file (config/quill.php) includes the following options:
return [
// Theme: 'snow' or 'bubble'
'theme' => 'snow',
// Placeholder text
'placeholder' => 'Digite seu texto aqui...',
// Editor dimensions
'height' => '300px',
'width' => '100%',
// Read-only mode
'readOnly' => false,
// Mobile-specific configurations
'mobile' => [
'simplifyToolbar' => true,
'height' => '200px',
],
// Quill.js modules configuration
'modules' => [...],
// Allowed formats
'formats' => [...],
];
Display a real-time character count with optional limit:
<livewire:quill-editor
:content="$content"
:config="[
'showCharacterCount' => true, // Enable counter
'characterLimit' => 500, // Optional: set max characters (null = no limit)
]"
/>
When the limit is exceeded, the counter turns red to alert the user.
Automatically save content to localStorage at regular intervals:
<livewire:quill-editor
:content="$content"
:config="[
'autoSave' => [
'enabled' => true, // Enable auto-save
'interval' => 30000, // Save every 30 seconds (in ms)
'showIndicator' => true, // Show "Saved" indicator
'key' => 'my-custom-key', // Optional: custom localStorage key
],
]"
/>
Programmatic Draft Control:
// In your Livewire component
public function restoreDraft(): void
{
$this->dispatch('restoreDraft');
}
public function clearDraft(): void
{
$this->dispatch('clearDraft');
}
Enable a full-screen button to expand the editor:
<livewire:quill-editor
:content="$content"
:config="[
'fullScreen' => [
'enabled' => true,
],
]"
/>
Enable direct image uploads with Livewire file handling:
<livewire:quill-editor
:content="$content"
:config="[
'imageUpload' => [
'enabled' => true,
'maxSize' => 2048, // Max size in KB (2MB)
'acceptedFormats' => ['jpeg', 'jpg', 'png', 'gif', 'webp'],
'disk' => 'public', // Storage disk
'path' => 'quill-images', // Storage path
],
]"
/>
Requirements:
php artisan storage:link to create the public storage symlinkHow it works:
When Image Upload is enabled, images can be resized interactively:
How to use:
<livewire:quill-editor
:content="$content"
:config="[
'height' => '400px',
'placeholder' => 'Start writing...',
// Character Counter
'showCharacterCount' => true,
'characterLimit' => 1000,
// Auto-save
'autoSave' => [
'enabled' => true,
'interval' => 15000,
'showIndicator' => true,
],
// Full-screen
'fullScreen' => [
'enabled' => true,
],
// Image Upload + Resize
'imageUpload' => [
'enabled' => true,
'maxSize' => 2048,
'acceptedFormats' => ['jpeg', 'jpg', 'png', 'gif', 'webp'],
],
]"
/>
Create a Livewire component to use the Quill editor:
php artisan make:livewire EditorComponent
You can pass configuration options directly to the component:
<livewire:quill-editor
:content="$meuConteudo"
:config="[
'theme' => 'bubble',
'height' => '400px',
'placeholder' => 'Escreva aqui...',
'modules' => [
'toolbar' => [
['bold', 'italic', 'underline'],
['link', 'image'],
]
]
]"
/>
You can also pass individual configuration parameters:
<livewire:quill-editor
:content="$meuConteudo"
:theme="'bubble'"
:height="'400px'"
:placeholder="'Escreva aqui...'"
:read-only="false"
/>
The component supports mobile-specific configurations:
<livewire:quill-editor
:content="$meuConteudo"
:config="[
'theme' => 'snow',
'height' => '400px',
'mobile' => [
'height' => '200px',
'simplifyToolbar' => true,
'toolbar' => [
['bold', 'italic'],
['link']
]
]
]"
/>
app/Livewire/EditorComponent.php<?php
namespace App\Livewire;
use Livewire\Component;
use Illuminate\Contracts\View\View;
use Livewire\Attributes\On;
class EditorComponent extends Component
{
public string $content = '';
#[On('quillUpdated')]
public function quillUpdated(string $content, string $editorId): void
{
$this->content = $content;
}
public function save(): void
{
// Save your content to database
// Example: Post::create(['content' => $this->content]);
session()->flash('message', 'Content saved successfully!');
}
public function render(): View
{
return view('livewire.editor-component');
}
}
resources/views/livewire/editor-component.blade.php<div>
<livewire:quill-editor :content="$content" />
<div class="mt-4">
<button wire:click="save" class="btn btn-primary">
Save Content
</button>
</div>
@if (session()->has('message'))
<div class="alert alert-success mt-2">
{{ session('message') }}
</div>
@endif
</div>
To load existing content from your database:
<?php
namespace App\Livewire;
use App\Models\Post;
use Livewire\Component;
use Livewire\Attributes\On;
class PostEditor extends Component
{
public Post $post;
public string $content = '';
public function mount(Post $post): void
{
$this->post = $post;
$this->content = $post->content ?? '';
}
#[On('quillUpdated')]
public function quillUpdated(string $content, string $editorId): void
{
$this->content = $content;
}
public function save(): void
{
$this->post->update([
'content' => $this->content
]);
session()->flash('message', 'Post updated successfully!');
}
public function render()
{
return view('livewire.post-editor');
}
}
<div>
<h2>Editing: {{ $post->title }}</h2>
<livewire:quill-editor
:content="$content"
:config="['height' => '400px']" />
<button wire:click="save" class="btn btn-success mt-3">
Update Post
</button>
</div>
You can now use multiple Quill editors on the same page:
<div class="grid grid-cols-2 gap-6">
{{-- Main Content Editor --}}
<div>
<h3>Main Content</h3>
<livewire:quill-editor
:content="$post->content"
:config="['height' => '300px', 'placeholder' => 'Write your main content...']" />
</div>
{{-- Excerpt Editor --}}
<div>
<h3>Excerpt</h3>
<livewire:quill-editor
:content="$post->excerpt"
:config="['height' => '150px', 'placeholder' => 'Write a short excerpt...']" />
</div>
</div>
You can customize the editor by passing a configuration array to the component:
<livewire:quill-editor
:content="$content"
:config="[
'theme' => 'bubble',
'placeholder' => 'Digite seu texto...',
'height' => '400px',
'readOnly' => false,
'modules' => [
'toolbar' => [
['bold', 'italic', 'underline', 'strike'],
['blockquote', 'code-block'],
[['header' => 1], ['header' => 2]],
[['list' => 'ordered'], ['list' => 'bullet']],
['link', 'image', 'video'],
['clean']
]
],
'formats' => [
'bold', 'italic', 'underline', 'strike',
'blockquote', 'code-block', 'header',
'list', 'bullet', 'link', 'image', 'video'
]
]"
/>
| Option | Type | Default | Description |
|---|---|---|---|
theme |
string | 'snow' |
Quill theme ('snow' or 'bubble') |
placeholder |
string | 'Digite aqui...' |
Placeholder text |
height |
string | '200px' |
Editor height |
readOnly |
boolean | false |
Read-only mode |
modules |
array | [] |
Quill modules configuration |
formats |
array | [] |
Allowed formats |
The Quill Editor component provides several useful methods:
// In your Livewire component
public function clearEditor(): void
{
$this->dispatch('clearEditor');
}
public function focusEditor(): void
{
$this->dispatch('focusEditor');
}
public function toggleEditor(bool $disabled = true): void
{
$this->dispatch('toggleEditor', disabled: $disabled);
}
public function selectAllEditor(): void
{
$this->dispatch('selectAllEditor');
}
// Access the editor component directly
$editor = $this->getChild('quill-editor');
// Get plain text (without HTML tags)
$plainText = $editor->getPlainText();
// Get word count
$wordCount = $editor->getWordCount();
// Check if editor is empty
$isEmpty = $editor->isEmpty();
// Set content programmatically
$editor->setContent('<p>New content here</p>');
The component dispatches the following events:
quillUpdatedFired when the editor content changes:
#[On('quillUpdated')]
public function handleContentUpdate(string $content, string $editorId): void
{
// Handle the updated content
$this->content = $content;
}
<livewire:quill-editor
:content="$content"
:config="[
'modules' => [
'toolbar' => [
[['size' => ['small', false, 'large', 'huge']]],
[['header' => [1, 2, 3, 4, 5, 6, false]]],
['bold', 'italic', 'underline', 'strike'],
[['color' => []], ['background' => []]],
[['script' => 'sub'], ['script' => 'super']],
[['header' => 1], ['header' => 2], 'blockquote', 'code-block'],
[['list' => 'ordered'], ['list' => 'bullet'], ['indent' => '-1'], ['indent' => '+1']],
[['direction' => 'rtl'], ['align' => []]],
['link', 'image', 'video', 'formula'],
['clean']
]
]
]"
/>
// In your Livewire component
public function uploadImage($image)
{
$path = $image->store('images', 'public');
$url = Storage::url($path);
// Inject image into editor
$this->dispatch('insertImage', url: $url);
}
:content attribute@stack('styles') is in your layout's <head>@stack('scripts') is before the closing </body> tagEnable debug mode to see console logs:
<livewire:quill-editor
:content="$content"
:config="['debug' => true]" />
MIT © Caique Bispo