AEM Frontend Stack – Granite & Coral UI
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.



Post Comment