Loading Now

AEM Frontend Stack – Granite & Coral UI

aem-granite-coral

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 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).
  • 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.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.