38

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,…

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 a granite:data child 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).
  • embedphysically 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.dialog for 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.

Ashish Sharma

I’ve always believed that collaboration is the engine of progress. While many say knowledge is power, I believe the true power lies in its distribution. To that end, I am building a curated knowledge base of my professional journey—refined by AI for maximum clarity and depth. Whether you’re here to master a new skill or sharpen an existing one, my goal is to provide a roadmap for your success. This collection will evolve as I do, and I welcome your insights and dialogue as we grow together.

Leave a Reply

Your email address will not be published. Required fields are marked *