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- Alwayscq/gui/components/authoring/dialog/richtextfieldLabel- Labelname- 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 linkunlink- 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 HTMLfindreplace- 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 }
</div>⚠️ IMPORTANT: Always use @ context='html' for RTE!
With Validation
<div data-sly-test="${properties.text}" class="content">
${properties.text }
</div>
<!-- Or with fallback -->
<div class="content">
${properties.text || '<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 }
</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 }</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
- ✅ useFixedInlineToolbar: Always
truefor better UX - ✅ Only necessary plugins: Don't enable everything
- ✅ Hierarchical headings: h2, h3, h4 (not h1)
- ✅ context='html' in HTL: Always for RTE
- ✅ Limit formatting: Bold, italic, lists, links are sufficient
- ✅ Test copy-paste: Verify behavior with Word/Google Docs
- ✅ Server-side validation: Sanitize HTML in the backend
- ✅ 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