A practical, end‑to‑end guide to AEM’s frontend stack—Granite (Touch UI foundation) and Coral UI—with copy‑pasteable examples for dialogs, custom widgets, authoring forms/pages, and client libraries (categories, dependencies, embed, allowProxy). Focused on patterns that work cleanly in AEM 6.5 and AEM as a Cloud Service.
Granite vs Coral UI (and where “Touch UI” fits)
- Granite UI is the foundation layer Adobe ships for building Touch UI. It has:
- Server‑side Sling components (under /libs/granite/ui/components/**) that render dialogs, forms, layouts, and datasources.
- Client‑side “Foundation” JS that wires up behaviors (validation, ajax forms, toggleables, wizards, etc.).\ Granite provides the vocabulary and building blocks for authoring UIs in AEM. [adobedocs.github.io], [developer.adobe.com]
- Coral UI is the visual/component library (CSS + JS/Web Components) used by Granite server components to render actual HTML (datepicker, dialog, popover, select, etc.). In AEM 6.5 the supported library is CoralUI 3. [developer.adobe.com], [developer.adobe.com]
- Together they power Touch UI (the modern authoring interface). Classic UI used ExtJS; Touch UI moved to Granite + Coral.
Granite in practice: dialogs, layouts & datasources
A minimal Touch UI dialog field (server‑side Granite)
<!-- /apps/stacknowledge/components/teaser/cqdialog/.content.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root
xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
xmlns:cq="http://www.day.com/jcr/cq/1.0"
xmlns:jcr="http://www.jcp.org/jcr/1.0"
jcr:primaryType="nt:unstructured"
jcr:title="Teaser"
sling:resourceType="cq/gui/components/authoring/dialog">
<content jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/container">
<items jcr:primaryType="nt:unstructured">
<title jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="Title"
name="./title"
required="{Boolean}true"/>
</items>
</content>
</jcr:root>
Granite’s server components live at /libs/granite/ui/components/coral/foundation/** and expose standard form fields (textfield, select, datepicker, checkbox, etc.).
Using a Granite DataSource to populate a Select dynamically
<!-- Inside dialog -->
<country
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/select"
fieldLabel="Country"
name="./country">
<datasource
jcr:primaryType="nt:unstructured"
sling:resourceType="stacknowledge/components/datasource/countries"/>
</country>
Your /apps/stacknowledge/components/datasource/countries (Sling script/servlet) returns a DataSource of resources with text & value for each option. This pattern centralizes choices and reuses them in many dialogs
If you need
custom data-*attributes or extra attributes per option, you can add agranite:datachild on each item resource; Granite renders them into markup for Coral Select
“Use of Touch UI library”: creating & using a custom Granite field (widget)
Sometimes you need behavior or markup that the stock fields don’t provide. The Granite pattern is
1. Create a field component that inherits from the generic field and overrides render.jsp:
/apps/stackowledge/widgets/colorhex
- sling:resourceSuperType = granite/ui/components/foundation/form/field
+ render.jsp (markup)
init.jsp from the supertype wires the label/description and passes the form value to your render.jsp. You only render the inner control.
2. Use the custom field in your dialog:
<color
jcr:primaryType="nt:unstructured"
sling:resourceType="myproj/widgets/colorhex"
fieldLabel="Brand Color"
name="./brandColor"/>
3. Attach client‑side behavior by loading a dialog‑scoped clientlib with extraClientlibs. In your JS, listen to foundation-contentloaded/dialog-ready, query your field via granite:id/granite:class, and apply logic. The Granite Foundation JS provides common events and APIs.
You can also “wrap” an existing field (e.g., PathField) and compute properties like rootPath dynamically at render time; keep server‑side for values that must be ready before the dialog opens.
Building a custom authoring form or page with Granite
You can build admin/authoring tools using Granite’s Form component and overlay them in a console if needed.
Form structure:
<!-- /apps/stacknowledge/tools/audit/page/.content.xml -->
<jcr:root
xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
xmlns:jcr="http://www.jcp.org/jcr/1.0"
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form"
method="post"
action="/bin/myproj/audit"
foundationForm="{Boolean}true"
async="{Boolean}true">
<items jcr:primaryType="nt:unstructured">
<site jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/pathfield"
fieldLabel="Site Root"
rootPath="/content"
name="./site"/>
<run jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/button"
text="Run Audit" type="submit" variant="primary"/>
</items>
</jcr:root>
Key form features—action, method, dataPath, foundationForm, async, loading masks, etc.—are documented in Granite’s Form reference. You can overlay or link such pages into the Touch UI consoles.
Coral UI for frontend developers (CSS/JS components)
Coral UI 3 provides touch‑first, accessible Web Components: coral-dialog, coral-select, coral-datepicker, coral-popover, coral-button, etc. AEM ships these under Granite clientlibs. Explore the API & “Configurator” examples in the Coral docs
Example – Datepicker (formatting uses Moment.js tokens):
<coral-datepicker
placeholder="Choose a date"
name="promoStart"
value="2026-02-23"
valueformat="YYYY-MM-DD"
displayformat="YYYY-MM-DD">
</coral-datepicker>
The Datepicker supports min/max, time, variants, and programmatic control.
Need UTC/zone‑aware values? Configure valueFormat/displayFormat with Moment tokens (e.g., include Z) to normalize stored values.
Using Coral elements on your page: create a clientlib that depends on coralui3 (and relevant Granite authoring libs) so Coral is available before your code runs
Client libraries (clientlibs): create, include, and master categories / dependencies / embed / allowProxy
Create a clientlib
Repository structure (recommended under /apps):
/apps/stacknowledge/clientlibs/clientlib-site
- .content.xml (cq:ClientLibraryFolder)
+ css/
site.css
css.txt
+ js/
site.js
js.txt
+ resources/ (images/fonts referenced by CSS)
logo.svg
.content.xml
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root
xmlns:cq="http://www.day.com/jcr/cq/1.0"
xmlns:jcr="http://www.jcp.org/jcr/1.0"
jcr:primaryType="cq:ClientLibraryFolder"
categories="[stacknowledge.site]"
dependencies="[core.wcm.components, coralui3]"
embed="[stacknowledge.components.base]"
allowProxy="{Boolean}true"/>
js.txt
#base=js
site.js
css.txt
#base=css
site.css
- categories — the names you include from pages or embed elsewhere.
- dependencies — other categories that must load before this library; the loader ensures order and includes them automatically (transitive).
- embed — physically merges the target categories’ CSS/JS into this clientlib’s output (not transitive). Use to reduce HTTP requests or to include non‑public libs.
- allowProxy=true — exposes /apps clientlibs via /etc.clientlibs (proxy servlet), which is the recommended, dispatcher‑friendly path in AEM 6.3+. Keep assets in a resources folder so they resolve via the proxy.
Why resources/? When proxied via /etc.clientlibs, referenced URLs (fonts/images) must sit under a resources folder to resolve correctly.
Include a clientlib on a page (HTL)
<sly data-sly-use.clientlib="/libs/granite/sightly/templates/clientlib.html"/>
<sly data-sly-call="${clientlib.all @ categories='stacknowledge.site'}"/>
This loads your CSS & JS; the referenced dependencies load first
Load JS/CSS only when a dialog opens (extraClientlibs)
For dialog‑specific behavior, avoid heavy global author libs. Put your author code in a small clientlib (e.g., stackowledge.author.teaser) and reference it from the dialog root:
<!-- in /cqdialog/.content.xml root -->
<jcr:root
…
sling:resourceType="cq/gui/components/authoring/dialog"
extraClientlibs="[stacknowledge.author.teaser]">
This ensures the clientlib is loaded only when this component’s dialog opens (not every dialog), which helps performance and avoids cross‑component side‑effects.
Best practice: keep
cq.authoring.dialogfor tiny, generic utilities only; put component‑specific code in its own category and guard your JS with a dialog resourceType check to prevent leaking into other dialogs.
Choosing dependencies vs embed (and categories)
- Use dependencies when you want logical ordering and automatic inclusion of other categories, keeping them as separate requests (good for caching & reuse).
- Use embed when you want a single payload (concatenated) and the embedded libs should not be addressable on their own. Remember: embed is not transitive.
Putting it together—custom widget + dialog behavior
Clientlib (author) JS skeleton that runs only for your dialog:
// /apps/stacknowledge/clientlibs/author-teaser/js/teaser-dialog.js
(function (document, $) {
"use strict";
var DIALOGRT = "stacknowledge/components/teaser";
function isTargetDialog($form) {
var rt = $form.find("input[name='./sling:resourceType']").val();
return rt === DIALOGRT;
}
$(document).on("dialog-ready", function () {
var $form = $(this).find("coral-dialog form.foundation-form");
if (!isTargetDialog($form)) return;
// Example: validate brandColor hex
var $field = $form.find("[name='./brandColor']");
$field.on("change", function () {
var ok = /^#?[0-9A-Fa-f]{6}$/.test(this.value);
if (!ok) {
Granite.UI.Foundation.Utils.notify("Invalid color (use 6‑digit hex).", "error");
}
});
});
})(document, Granite.$);
Load it with extraClientlibs as shown above. Guarding by resourceType is a proven pattern for dialog safety.
FAQ‑style clarifications
Q. When should I use a Granite DataSource vs. hardcoded <items>?
Use a DataSource when the option list changes often, is shared by multiple components, or must be computed at runtime (e.g., site‑specific lists).
Q. Is “embed” or “dependencies” better?
Prefer dependencies for modularity and cache reuse. Use embed for bundling small internal libs to reduce requests. Remember embed is not transitive.
Q. How do I include Coral on a custom page?
Create a clientlib that depends on coralui3 (and sometimes granite.ui.coral.foundation), then include your category on the page.
Wrapping up
With Granite you define structure and behavior for authoring (dialogs, forms, datasources), while Coral UI gives you accessible, consistent UI components. Clientlibs—especially categories, dependencies, embed, and allowProxy—are the glue that make your code predictable and performant across author and publish.