AEM Dialog Components #6: RichText Editor - Formatted Content

AEM Dialog Components #6: RichText Editor (RTE)

What is the RichText Editor?

The RichText Editor (RTE) is a Granite UI component that allows authors to create formatted content with bold, italic, lists, links, tables and much more.

Common use cases:

  • Editorial content
  • Long descriptions with formatting
  • Articles and blog posts
  • Text with internal links
  • Content with lists and tables

Basic Configuration

Minimal XML Dialog

<text
    jcr:primaryType="nt:unstructured"
    sling:resourceType="cq/gui/components/authoring/dialog/richtext"
    fieldLabel="Text"
    name="./text"
    useFixedInlineToolbar="{Boolean}true"/>

Essential properties:

  • sling:resourceType - Always cq/gui/components/authoring/dialog/richtext
  • fieldLabel - Label
  • name - JCR path (always use ./)
  • useFixedInlineToolbar - Fixed inline toolbar (recommended)

Main Properties

1. useFixedInlineToolbar

<text
    sling:resourceType="cq/gui/components/authoring/dialog/richtext"
    fieldLabel="Text"
    name="./text"
    useFixedInlineToolbar="{Boolean}true"/>

Behavior:

  • true - Toolbar always visible at the top (better UX)
  • false - Toolbar appears on text selection (default)

Best practice: Always use true for better authoring!


2. Required

<text
    sling:resourceType="cq/gui/components/authoring/dialog/richtext"
    fieldLabel="Article Content"
    name="./text"
    required="{Boolean}true"
    useFixedInlineToolbar="{Boolean}true"/>

RTE Plugin Configuration

The RTE is configured through plugins that enable specific features. Each plugin must be declared under the <rtePlugins> node.

Basic Structure with Plugins

<text
    jcr:primaryType="nt:unstructured"
    sling:resourceType="cq/gui/components/authoring/dialog/richtext"
    fieldLabel="Text"
    name="./text"
    useFixedInlineToolbar="{Boolean}true">

    <!-- UI Configuration -->
    <uiSettings jcr:primaryType="nt:unstructured">
        <cui jcr:primaryType="nt:unstructured">
            <!-- Inline toolbar -->
            <inline
                jcr:primaryType="nt:unstructured"
                toolbar="[format#bold,format#italic,format#underline,links#modifylink,lists#unordered,lists#ordered]">
                <popovers jcr:primaryType="nt:unstructured">
                    <format
                        jcr:primaryType="nt:unstructured"
                        ref="format"/>
                    <lists
                        jcr:primaryType="nt:unstructured"
                        ref="lists"/>
                    <links
                        jcr:primaryType="nt:unstructured"
                        ref="links"/>
                </popovers>
            </inline>
        </cui>
    </uiSettings>

    <!-- RTE Plugins -->
    <rtePlugins jcr:primaryType="nt:unstructured">

        <!-- Format (Bold, Italic, Underline) -->
        <format
            jcr:primaryType="nt:unstructured"
            features="[bold,italic,underline]"/>

        <!-- Lists -->
        <lists
            jcr:primaryType="nt:unstructured"
            features="[ordered,unordered]"/>

        <!-- Links -->
        <links
            jcr:primaryType="nt:unstructured"
            features="[modifylink,unlink]"/>

    </rtePlugins>

</text>

Available Plugins

1. Format (Bold, Italic, Underline)

<format
    jcr:primaryType="nt:unstructured"
    features="[bold,italic,underline]"/>

Generates: <strong>, <em>, <u>


2. Lists (Ordered, Unordered)

<lists
    jcr:primaryType="nt:unstructured"
    features="[ordered,unordered,indent,outdent]"/>

Generates: <ul>, <ol>, <li>


3. Links (Modify, Remove)

<links
    jcr:primaryType="nt:unstructured"
    features="[modifylink,unlink]">
    <linkDialogConfig jcr:primaryType="nt:unstructured">
        <linkDialogFields jcr:primaryType="nt:unstructured">
            <targetToggle
                jcr:primaryType="nt:unstructured"
                checked="{Boolean}false"/>
        </linkDialogFields>
    </linkDialogConfig>
</links>

Features:

  • modifylink - Create/modify link
  • unlink - Remove link

Generates: <a href="...">


4. Justify (Alignment)

<justify
    jcr:primaryType="nt:unstructured"
    features="[justifyleft,justifycenter,justifyright]"/>

5. Paraformat (Heading, Paragraphs)

<paraformat
    jcr:primaryType="nt:unstructured"
    features="*">
    <formats jcr:primaryType="nt:unstructured">
        <p
            jcr:primaryType="nt:unstructured"
            description="Paragraph"
            tag="p"/>
        <h2
            jcr:primaryType="nt:unstructured"
            description="Heading 2"
            tag="h2"/>
        <h3
            jcr:primaryType="nt:unstructured"
            description="Heading 3"
            tag="h3"/>
        <h4
            jcr:primaryType="nt:unstructured"
            description="Heading 4"
            tag="h4"/>
    </formats>
</paraformat>

Generates: <h2>, <h3>, <h4>, <p>


6. Subsuperscript (Subscript, Superscript)

<subsuperscript
    jcr:primaryType="nt:unstructured"
    features="[subscript,superscript]"/>

Generates: <sub>, <sup>


7. Table (Tables)

<table
    jcr:primaryType="nt:unstructured"
    features="[createtable,removetable,insertrow,removerow,insertcolumn,removecolumn,cellprops,mergecells,splitcell,selectrow,selectcolumn]"/>

Generates: <table>, <tr>, <td>, <th>


8. Misctools (Code, Find/Replace)

<misctools
    jcr:primaryType="nt:unstructured"
    features="[sourceedit,findreplace]"/>
  • sourceedit - Edit raw HTML
  • findreplace - Find and replace

Practical Examples

Example 1: Simple RTE (Bold, Italic, Lists, Link)

<description
    jcr:primaryType="nt:unstructured"
    sling:resourceType="cq/gui/components/authoring/dialog/richtext"
    fieldLabel="Description"
    name="./description"
    useFixedInlineToolbar="{Boolean}true">

    <uiSettings jcr:primaryType="nt:unstructured">
        <cui jcr:primaryType="nt:unstructured">
            <inline
                jcr:primaryType="nt:unstructured"
                toolbar="[format#bold,format#italic,links#modifylink,lists#unordered,lists#ordered]">
                <popovers jcr:primaryType="nt:unstructured">
                    <format
                        jcr:primaryType="nt:unstructured"
                        ref="format"/>
                    <lists
                        jcr:primaryType="nt:unstructured"
                        ref="lists"/>
                    <links
                        jcr:primaryType="nt:unstructured"
                        ref="links"/>
                </popovers>
            </inline>
        </cui>
    </uiSettings>

    <rtePlugins jcr:primaryType="nt:unstructured">
        <format
            jcr:primaryType="nt:unstructured"
            features="[bold,italic]"/>
        <lists
            jcr:primaryType="nt:unstructured"
            features="[ordered,unordered]"/>
        <links
            jcr:primaryType="nt:unstructured"
            features="[modifylink,unlink]"/>
    </rtePlugins>

</description>

Example 2: Complete RTE (Editorial Article)

<articleText
    jcr:primaryType="nt:unstructured"
    sling:resourceType="cq/gui/components/authoring/dialog/richtext"
    fieldLabel="Article Content"
    name="./articleText"
    required="{Boolean}true"
    useFixedInlineToolbar="{Boolean}true">

    <uiSettings jcr:primaryType="nt:unstructured">
        <cui jcr:primaryType="nt:unstructured">
            <inline
                jcr:primaryType="nt:unstructured"
                toolbar="[format#bold,format#italic,format#underline,#paraformat,links#modifylink,lists#unordered,lists#ordered,justify#justifyleft,justify#justifycenter,justify#justifyright]">
                <popovers jcr:primaryType="nt:unstructured">
                    <format
                        jcr:primaryType="nt:unstructured"
                        ref="format"/>
                    <paraformat
                        jcr:primaryType="nt:unstructured"
                        ref="paraformat"/>
                    <lists
                        jcr:primaryType="nt:unstructured"
                        ref="lists"/>
                    <links
                        jcr:primaryType="nt:unstructured"
                        ref="links"/>
                    <justify
                        jcr:primaryType="nt:unstructured"
                        ref="justify"/>
                </popovers>
            </inline>
        </cui>
    </uiSettings>

    <rtePlugins jcr:primaryType="nt:unstructured">
        <format
            jcr:primaryType="nt:unstructured"
            features="[bold,italic,underline]"/>
        <paraformat
            jcr:primaryType="nt:unstructured"
            features="*">
            <formats jcr:primaryType="nt:unstructured">
                <p
                    jcr:primaryType="nt:unstructured"
                    description="Paragraph"
                    tag="p"/>
                <h2
                    jcr:primaryType="nt:unstructured"
                    description="Heading 2"
                    tag="h2"/>
                <h3
                    jcr:primaryType="nt:unstructured"
                    description="Heading 3"
                    tag="h3"/>
                <h4
                    jcr:primaryType="nt:unstructured"
                    description="Heading 4"
                    tag="h4"/>
            </formats>
        </paraformat>
        <lists
            jcr:primaryType="nt:unstructured"
            features="[ordered,unordered,indent,outdent]"/>
        <links
            jcr:primaryType="nt:unstructured"
            features="[modifylink,unlink]"/>
        <justify
            jcr:primaryType="nt:unstructured"
            features="[justifyleft,justifycenter,justifyright]"/>
    </rtePlugins>

</articleText>

Using in HTL

HTML Rendering

<!-- RTE already generates HTML, use context='html' -->
<div class="article-content">
  ${properties.text @ context='html'}
</div>

⚠️ IMPORTANT: Always use @ context='html' for RTE!


With Validation

<div data-sly-test="${properties.text}" class="content">
  ${properties.text @ context='html'}
</div>

<!-- Or with fallback -->
<div class="content">
  ${properties.text @ context='html' || '<p>No content available</p>'}
</div>

With Sling Model

@Model(adaptables = Resource.class)
public class ArticleModel {

    @ValueMapValue
    private String articleText;

    public String getArticleText() {
        return articleText;
    }

    public boolean hasContent() {
        return articleText != null && !articleText.isEmpty();
    }

    /**
     * Strip HTML for excerpt
     */
    public String getPlainTextExcerpt(int maxLength) {
        if (articleText == null) {
            return "";
        }

        // Strip HTML tags
        String plainText = articleText.replaceAll("<[^>]*>", "");

        // Truncate
        if (plainText.length() > maxLength) {
            return plainText.substring(0, maxLength) + "...";
        }

        return plainText;
    }

    /**
     * Word count
     */
    public int getWordCount() {
        if (articleText == null) {
            return 0;
        }

        String plainText = articleText.replaceAll("<[^>]*>", "");
        return plainText.split("\\s+").length;
    }
}

HTL with Model:

<article data-sly-use.model="com.mysite.models.ArticleModel">

  <div data-sly-test="${model.hasContent}" class="article-body">
    ${model.articleText @ context='html'}
  </div>

  <div class="article-meta">
    <span>Word count: ${model.wordCount}</span>
  </div>

  <!-- Plain text excerpt for SEO -->
  <meta name="description" content="${model.getPlainTextExcerpt(160)}"/>

</article>

RTE vs Textarea

RTE Textarea
Formatting Yes (bold, link, lists) No (plain text)
HTML Output Yes No
Complexity High Low
Performance Heavier Light
Use Case Editorial content Simple text, metadata

Rule: Only use RTE if formatting is needed!


Common Issues

❌ Issue 1: HTML not rendered

<!-- WRONG - shows HTML tags as text -->
<div>${properties.text}</div>

<!-- CORRECT - renders HTML -->
<div>${properties.text @ context='html'}</div>

❌ Issue 2: Plugin not working

<!-- WRONG - plugin declared but not in toolbar -->
<rtePlugins>
    <format features="[bold,italic]"/>
</rtePlugins>

<!-- Toolbar does not include format#bold -->
<inline toolbar="[lists#unordered]"/>

<!-- CORRECT - toolbar includes the plugins -->
<inline toolbar="[format#bold,format#italic,lists#unordered]"/>

❌ Issue 3: Broken links for internal pages

Problem: RTE saves links as /content/mysite/page without .html

HTL Solution: Post-process the content

public String getProcessedText() {
    if (articleText == null) {
        return "";
    }

    // Add .html to internal links
    return articleText.replaceAll(
        "href=\"(/content/[^\"]+)\"",
        "href=\"$1.html\""
    );
}

❌ Issue 4: Invalid HTML saved

Problem: Authors copy HTML from Word/Email

Solution: Use paste plugin with cleanup options

<paste
    jcr:primaryType="nt:unstructured"
    features="[plaintext,wordhtml,markdown]"
    defaultPasteMode="plaintext"/>

Best Practices

  1. useFixedInlineToolbar: Always true for better UX
  2. Only necessary plugins: Don't enable everything
  3. Hierarchical headings: h2, h3, h4 (not h1)
  4. context='html' in HTL: Always for RTE
  5. Limit formatting: Bold, italic, lists, links are sufficient
  6. Test copy-paste: Verify behavior with Word/Google Docs
  7. Server-side validation: Sanitize HTML in the backend
  8. Accessibility: Configure plugins for semantic HTML

Global RTE Configuration

You can configure the RTE globally in:

/apps/cq/gui/components/authoring/editors/clientlibs/core/rte/config.json

This allows reusable configurations across all components.


Advanced Plugins

Styles (Custom CSS Classes)

<styles
    jcr:primaryType="nt:unstructured"
    features="*">
    <styles jcr:primaryType="nt:unstructured">
        <highlight
            jcr:primaryType="nt:unstructured"
            cssName="text-highlight"
            text="Highlight"/>
        <quote
            jcr:primaryType="nt:unstructured"
            cssName="blockquote"
            text="Quote"
            tag="blockquote"/>
    </styles>
</styles>

Next Lesson

This concludes the basic series on AEM dialog components! In the next guides we'll see advanced components like FileUpload, ColorField, NumberField and others.

Resources:


Guide #6 of the AEM Dialog Components series - ← Previous lesson

Complete series: Textfield | Textarea | Checkbox | Select | PathBrowser | RichText