makraz/ux-quill
makraz/ux-quill is a Laravel package that adds a ready-to-use Quill rich-text editor Blade component. Drop it into forms, customize the toolbar, and store HTML or Delta content with minimal setup—ideal for clean WYSIWYG editing in Laravel apps.
Read this first: despite the name, ux-quill is a Symfony UX bundle, not a Laravel package. There is no composer require path that "just works" in a Laravel app — the form type, Twig rendering, and Stimulus wiring assume Symfony's Form component, Twig, and symfony/stimulus-bundle. For a Laravel developer, treat this as a reference implementation or a package to port, not a drop-in dependency.
Minimal path if you're evaluating it:
composer require makraz/ux-quill in a Symfony 6.4/7.x/8.x app (PHP ≥ 8.1).symfony/stimulus-bundle ≥ 2.9.1 is installed and your asset pipeline (AssetMapper or Webpack Encore) is running.use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class ArticleType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('title')
->add('body', QuillType::class, [
'theme' => 'snow',
'placeholder' => 'Write something…',
'minHeight' => '20rem',
]);
}
}
input/change events so Symfony validation and Live Components behave.First use case: a single rich-text field on a create/edit form. That's the well-trodden path — don't try to retrofit it into a complex custom-field setup on day one.
EasyAdmin shortcut: if you're using EasyAdmin, QuillAdminField is a one-liner config; check that first before hand-rolling a form type.
Basic form field. QuillType behaves like any other Symfony field — constraints, required, empty_data, and validation all work because the underlying value is a plain hidden input containing HTML. Persist it like any string column.
Two themes, pick per-field. theme => 'snow' for a classic toolbar (default) and theme => 'bubble' for a floating tooltip toolbar. Useful when you have a sidebar-heavy layout where a top toolbar eats vertical space.
Toolbar customization has three modes worth knowing:
'#my-toolbar' (CSS selector) → render your own toolbar markup in Twig and let Quill bind to it.false → no toolbar at all (pairs well with readOnly => true for preview screens).Read-only / preview mode. Set readOnly => true and render the same form type in a "view" context. Cleaner than duplicating a Twig sanitizer path.
Event-driven extensions. The Stimulus controller exposes three DOM events — this is where most real customization lives:
document.addEventListener('quill:options', (e) => {
// mutate e.detail.options before Quill boots
});
document.addEventListener('quill:connect', (e) => {
// Quill is live; grab e.detail.quill if needed
});
document.addEventListener('quill:change', (e) => {
const { html } = e.detail;
// live word count, autosave draft, preview pane, etc.
});
Live Components integration. The bundle already handles data-live-ignore so the editor doesn't get clobbered on re-render. If you're writing a custom Stimulus controller around a Live Component, mimic that attribute.
Asset pipeline. Works with both AssetMapper and Webpack Encore. With Encore, confirm Quill is actually bundled — a missing Quill import is the most common "nothing renders" cause.
Porting to Laravel. If you want Quill-in-Laravel specifically, you're better off taking the ideas here (hidden input + two-way sync + the three event names) and wiring them to your own Blade component + Alpine or a small Stimulus controller via @hotwired/stimulus. The bundle's value to a Laravel dev is mostly as a clean spec.
It's Symfony, not Laravel. No service provider, no config/quill.php, no Blade directives. Any Laravel integration requires a port. Be honest with your team about that before composer require-ing anything.
Experimental = unstable. The release notes explicitly say backwards compatibility is not guaranteed between releases. Pin the exact version ("makraz/ux-quill": "0.1.0") and treat upgrades as mini-migrations. Do not put this on a client project's critical path yet.
HTML is stored raw. The bundle stores whatever Quill produces into a hidden field — no server-side sanitization is mentioned. Always sanitize on persistence or render (e.g. HTMLPurifier on the Symfony side; mews/purifier / HTMLPurifier on the Laravel side if you port). This is the single highest-risk item in the release.
minHeight type quirk. It accepts an int (pixels) or a CSS string ('20rem'). Passing a bare numeric string like '400' will produce invalid CSS — pass 400 (int) or '400px'.
Hidden input + validation ordering. The controller fires native input/change events specifically for form validation compatibility, but if you add your own JS validator, hook quill:change rather than polling the hidden field — the sync is async relative to keystrokes.
Live Component re-renders. data-live-ignore is set automatically on the field, but if you nest the editor inside a component that swaps DOM on every model update, you can still lose editor state. Wrap the editor in a data-live-ignore container yourself as belt-and-braces.
quill:options fires once per initialization. Mutating config after quill:connect has no effect — use the options event if you need to inject custom formats, modules, or bounds.
Bubble theme + toolbar config don't mix well. The bubble theme ignores a top toolbar; passing a custom toolbar selector with theme => 'bubble' produces confusing results. Choose one styling strategy.
EasyAdmin users: QuillAdminField is the fast path, but check the field's supported options against QuillType — the config surface may lag behind the form type. Fall back to QuillType in a custom CRUD form if you need an option the admin field doesn't expose.
Debugging checklist when the editor doesn't appear:
stimulus-bundle ≥ 2.9.1 and controllers are registered.quill:connect listener fires.Where to extend: the three events are the intended extension points. Avoid reaching into the hidden input directly; the controller owns that node and will overwrite it on the next change.
How can I help you explore Laravel packages today?