Skip to content

Usage

Basic Usage

In a form, use QuillType. It works like a classic Type except it has more options:

php
use Ehyiah\QuillJsBundle\Form\QuillType;
use Symfony\Component\Form\FormBuilderInterface;

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        // ...
        ->add('myField', QuillType::class)
    ;
}

Toolbar Presets (fast configuration with built-in presets)

The bundle ships with a few ready-made toolbar compositions exposed by QuillGroup. Pick the preset that matches the context, or assemble your own with QuillGroup::build(...).

PresetIntended useNotable fields
QuillGroup::buildMinimal()Comments, short descriptions, note fields.bold, italic, underline, link, clean
QuillGroup::buildForNewsletter()Newsletter / email content: light formatting, no technical fields.text emphasis, headings, colors, alignment, lists, image
QuillGroup::buildAdvanced()Rich editorial content for blog articles.full text formatting, headings, lists, media, code, table
QuillGroup::buildWithAllFields() see below warningEvery available field, including specialised ones (formula, RTL direction, script, font, emoji).all

WARNING

QuillGroup::buildWithAllFields() is deprecated and will be removed in a future version (v4.0.0). Because all the fields cannot be included anymore (e.g. ImageGalleryField cannot be included as this field require an explicit configuration in order to work).

Example:

php
use Ehyiah\QuillJsBundle\DTO\QuillGroup;
use Ehyiah\QuillJsBundle\Form\QuillType;

$builder->add('content', QuillType::class, [
    'quill_options' => [
        QuillGroup::buildForNewsletter(),
    ],
]);

Minimal Display working example with AssetMapper:

twig
{# templates/my_template.html.twig #}
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>{% block title %}{% endblock %}</title>

        {% block stylesheets %}
            {{ quill_content_styles() }}
        {% endblock %}
    </head>

    <body>
        {% block body %}
            <twig:QuillContent value="{{ post.content }}" />
        {% endblock %}
    </body>
</html>

Detailled Display Result using Built-in mechanisms

How you display the content depends on the style option used during entry (see Quill Options).

The easiest way to display Quill content is to use the provided Twig component.

It handles the necessary HTML wrappers and modes automatically.

It does not load any CSS by itself — you stay in control of what stylesheets are shipped to the public page.

twig
{# Default mode (class) #}
<twig:QuillContent value="{{ post.content }}" />

{# Inline mode #}
<twig:QuillContent value="{{ post.content }}" style="inline" />

{# Custom HTML tag and extra classes (extra attributes are passed through to the wrapper) #}
<twig:QuillContent tag="article" value="{{ post.content }}" class="my-custom-class" />

<twig:QuillContent> always wraps content with the ql-snow class. Quill's bubble theme is an editor look (popover toolbar) and brings no benefit on a read-only render — the produced HTML is identical. If you really want it, render the wrapper manually (see Manual Display below) and load quill.bubble.css via quill_content_styles('bubble').

Loading the required CSS

When using the class style, the rendered HTML needs CSS to look right.

The <twig:QuillContent> component does not load any stylesheet by itself — you decide what to ship to the page.

AssetMapper

TIP

The easiest way to ship the required CSS is to use the provided Twig helper function in your layout head.

A Twig helper is shipped to emit the corresponding <link> tags. Call it once in your layout (it is idempotent per request — each stylesheet is emitted at most once even if the helper is called several times):

twig
{# in your <head> — minimum: snow theme + structural rules #}
{{ quill_content_styles() }}

{# bubble theme instead of snow #}
{{ quill_content_styles('bubble') }}

{# also load the opt-in cosmetic stylesheet for mentions #}
{{ quill_content_styles('snow', true) }}

Signature: quill_content_styles(theme = 'snow', cosmetic = false).

The helper resolves URLs through AssetMapper. Make sure the assets are reachable through your importmap, e.g.:

bash
php bin/console importmap:require quill/dist/quill.snow.css

If an asset cannot be resolved (not in the importmap, AssetMapper not installed…), no <link> is emitted for it — silently. The bundle's own stylesheets (quill-content.css and quill-content-theme.css) are already exposed by the bundle, you don't need to require them manually.

Where to call it, All pages rendering Quill content:

Put the helper in the <head> of your layout, inside the stylesheets block so child templates can override. Two common patterns:

twig
{# templates/base.html.twig #}
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>{% block title %}{% endblock %}</title>

        {% block stylesheets %}
            {{ quill_content_styles() }}
        {% endblock %}

        {% block javascripts %}
            {% block importmap %}{{ importmap('app') }}{% endblock %}
        {% endblock %}
    </head>
    <body>
        {% block body %}{% endblock %}
    </body>
</html>

The helper is idempotent per request — calling it from both base.html.twig and a child template is safe, each <link> is emitted only once.

Webpack Encore / your own bundler

Import the stylesheets explicitly in your CSS entry point. The helper does nothing here — load only the layers you actually want:

css
@import 'quill/dist/quill.snow.css';

@import '@ehyiah/ux-quill/dist/styles/quill-content.css';

@import '@ehyiah/ux-quill/dist/styles/quill-content-theme.css';

You can take a look at the css layers in the CSS layers section to know which ones you need.

Loading the required JavaScript (maps)

<twig:QuillContent> renders the saved HTML, but maps (.ql-map elements) are only interactive once initialized by the quill-maps Stimulus controller.

quill_content_scripts() emits that controller element. Call it once, on pages whose content contains maps — it is idempotent per request:

twig
<twig:QuillContent :value="content" />

{{ quill_content_scripts() }}

When is it required?

quill_content_scripts() is only needed when the rendered content contains blots that require JavaScript to display (currently: maps). The table below lists the fields and modules that need this call — it will grow as new interactive blots are added.

FieldModuleRequired?
MapFieldMapModule✅ Yes — on pages rendering content that contains maps

It is not needed on:

  • Pages whose content has no maps.
  • The editor page — the form already loads the editor's own JavaScript.

Requirements

  • AssetMapper / importmap: the page must render the importmap (importmap('app'), usually in base.html.twig) and bin/console importmap:install must have been run after updating the bundle.
  • Webpack Encore: the quill-maps controller must be listed in assets/controllers.json (run bin/console ux:controllers:dump after updating the bundle), then rebuild your assets (yarn watch / yarn build).

Troubleshooting

  • Map not initialized → check that the <div data-controller="ehyiah--ux-quill--quill-maps"> element is present, that the controller is registered (browser console: Controller not found?), and that no JavaScript error occurred.
  • 404 on /assets/@ehyiah/ux-quill/dist/modules/map-utils.js → the module is being loaded without the importmap (AssetMapper) — render it via importmap('app') and re-run bin/console importmap:install.

The quill-maps controller also dispatches lifecycle events (ux-quill:map:before-init, ux-quill:map:initialized, ux-quill:map:error, ux-quill:maps:completed) — see Map events.

Manual Display

If you prefer to wrap the content manually:

1. Default Styling (Class based)

By default, Quill uses CSS classes (e.g., ql-align-center, ql-indent-1) to format content.

Requirement: You must include the Quill CSS on your frontend page, and the content must be wrapped in specific classes:

twig
{# 1. Ensure Quill CSS is loaded on this page, exemple #}
<link href="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.snow.css" rel="stylesheet" />

{# 2. Wrap the content #}
<div class="ql-snow">
    <div class="ql-editor">
        {{ myField|raw }}
    </div>
</div>

2. Inline Styling

If you configured the field with 'style' => 'inline', Quill writes styles directly into the HTML tags (e.g., style="text-align: center;").

Pros:

  • Content looks correct even without the Quill CSS on the frontend.
  • Ideal for emails or RSS feeds.

Cons & Limitations:

  • Incomplete coverage: While alignment, colors, fonts, and indentation are handled inline, some complex elements still require CSS:
    • Videos: The .ql-video class is needed for proper sizing.
    • Code Blocks: The .ql-syntax class and a syntax highlighter (like highlight.js) are needed.
    • Formulas: KaTeX CSS is still required.
  • HTML Weight: The resulting HTML is slightly heavier due to repeated style attributes.
twig
<div>{{ myField|raw }}</div>

For the most basic usage, this is only what you have to do.

Explaining the CSS layers

There are up to three separate layers, and you load only what you need:

StylesheetTwig call (with assetMapper)When do you need itFieldsModulesRequired?
quill.snow.css{{ quill_content_styles() }}Always when using class styleAll quill built-in fields (ImageField is overriden in this bundle)✅ Yes
@ehyiah/ux-quill/dist/styles/quill-content.cssAlways included with {{ quill_content_styles() }}Structural rules for advanced blots — page-break, video, image-figurePageBreakField, VideoField, ImageFieldPageBreakModule, ImageSelectionModule⚠️ Only if you use these fields
@ehyiah/ux-quill/dist/styles/quill-content-theme.css{{ quill_content_styles(cosmetic=true) }}Cosmetic built-in styleMentionModule❌ Optional — style them yourself otherwise