Building Custom AEM Components with Granite UI and Touch Dialogs

Adobe Experience Manager has evolved into a robust platform for assembling digital experiences, and at the heart of that capability sits the component model. For developers in Sydney, Melbourne, Brisbane and beyond, mastering the modern touch-enabled dialog system is a practical skill that pays dividends on every project. Granite UI replaces the legacy ExtJS-based Classic UI dialogs, and while the learning curve can feel steep at first, the payoff is a cleaner authoring experience and tighter alignment with Coral-based interfaces that authors actually enjoy using.

The shift from Classic to Touch UI dialogs reshaped how Australian teams approach component configuration. Where developers once wrestled with dialog.xml files and xtypes, they now work with cq:dialog nodes containing JSON-like structures that map to Granite UI resource types. This article walks through the practical steps of building a custom component from scratch, covering project setup, dialog configuration, and the integration patterns that keep everything maintainable.

Foundations of Granite UI in Modern AEM

Granite UI is the underlying framework that powers the authoring interface in Adobe Experience Manager 6 and AEM as a Cloud Service. It provides a library of Sling-based components, each with its own resource type, that authors interact with through a touch-friendly layout. For practitioners who maintain legacy CQ5 installations, the transition to AEM 6.2 introduced significant architectural changes that are worth understanding before diving into component work. Teams picking up older codebases often find value in reviewing transitioning from Adobe CQ5 to AEM 6.2 key migration strategies to ground their approach in the modern paradigm.

At the component level, a Granite UI dialog is simply a Sling resource hierarchy under a cq:dialog node. Each field is a resource with a sling:resourceType pointing to a Granite widget, plus a handful of configuration properties. The framework reads this structure at runtime and renders the appropriate input controls. A text field becomes granite/ui/components/coral/foundation/form/textfield, a path browser becomes granite/ui/components/coral/foundation/form/pathbrowser, and so on. Understanding this mapping is essential because it determines exactly what authors see on the page properties screen.

For Australian teams working on government projects in Canberra or commercial builds in Perth, the consistent vocabulary across component dialogs also simplifies onboarding. New developers can predict where configuration lives, and content authors benefit from a familiar interaction model across every component in the system. Anyone looking to deepen their grasp of these patterns can browse the conference speakers page for recorded sessions from experienced practitioners.

Structuring Your Component Project

Before writing any dialog code, the folder structure under /apps deserves careful thought. A clean component layout separates the script, the template, the dialog definition, and any client libraries. A practical convention places the dialog under /apps/<project>/components/<component>/cq:dialog, the HTL script at /apps/<project>/components/<component>/<component>.html, and supporting client libraries under /apps/<project>/components/<component>/clientlibs. Keeping these in their conventional locations makes the component easier to find when troubleshooting and easier to deploy through a content package.

Naming conventions matter too. Adobe recommends lowercase, hyphen-separated component names that mirror their node names. A component called teaser lives in a folder called teaser, with a dialog node called cq:dialog and a primary script called teaser.html. This predictability is one of the underrated strengths of AEM development, particularly for distributed teams in Adelaide or regional centres who collaborate on shared codebases.

When working with Maven and the AEM Project Archetype, the same conventions are baked into the generated structure. The archetype's ui.apps module holds the component definitions, while ui.frontend handles any decoupled front-end build steps. Developers contributing to projects for broadcasters in Sydney or retailers in Melbourne typically find that following the archetype's structure from day one eliminates a class of deployment headaches later.

Building the Touch Dialog Itself

The dialog definition starts with the root node and the appropriate sling:resourceType. A typical tabbed dialog uses granite/ui/components/coral/foundation/container as the outer container, with each tab defined as a child item. Inside each tab, a granite/ui/components/coral/foundation/form/fieldset lays out the form fields vertically, and individual fields sit beneath it. The jcr:title property supplies the label that authors see, while name provides the property key under which the value is saved.

Multifield components deserve special attention because they appear in nearly every real-world project. A multifield is essentially a repeatable container that holds other widgets. For example, a list of call-to-action links might use granite/ui/components/coral/foundation/form/multifield as the container, with each item containing a text field and a path browser. The dialog definition nests these resource types inside one another, and Granite UI handles the add and remove controls automatically. Practitioners refining their craft often consult session recordings to see these nested structures demonstrated in detail.

Validation is configured per field through properties like required, regex, and custom Granite validators. A common pattern is to ensure that an image path resolves to an existing asset, or that a date falls within a sensible range. Validators run on the client before the dialog submits, providing immediate feedback to authors. For accessibility-conscious teams, pairing validation with WCAG-aware auditing is a sensible practice that helps ensure custom dialogs do not introduce barriers for content authors using assistive technology.

Hidden fields and context-driven defaults add another layer of sophistication. A dialog can reference the current page path through Granite's expression language, or read inherited properties from the parent page. These mechanisms allow a single dialog to behave intelligently across different contexts without requiring component variants for every scenario.

Connecting Dialogs to Sightly and HTL

Once the dialog saves values to the component's properties, the HTL script consumes them through the properties object. For a text field saved as heading, the script references ${properties.heading}. For a multifield, the values are read as a list and iterated with data-sly-list. This declarative binding keeps the view layer clean and lets authors see changes immediately in the preview mode of the touch UI editor.

Backing the HTL with a Sling Model is the recommended pattern for any non-trivial component. A model class annotated with @Model(adaptables = SlingHttpServletRequest.class) exposes typed getters for each dialog property. This gives the template access to precomputed values, formatted dates, resolved asset metadata, and any business rules that should apply before rendering. For Brisbane-based teams building financial services components, models provide a single place to enforce compliance-related transformations that would otherwise be repeated across every script.

Client libraries play a supporting role, particularly for components that require interactive behaviour on the published site. A dialog might save a colour picker value, and a front-end script reads that value through data-* attributes emitted by HTL. The discipline of separating authoring configuration from published presentation keeps the dialog focused on what authors need to set, while the front-end handles rendering optimisations and progressive enhancement.

Debugging and Iterating in Practice

Dialogs rarely work perfectly on the first try, and efficient debugging is a hallmark of an experienced AEM developer. The Sling Resource Resolver at /system/console/resolver shows the merged view of dialog properties after inheritance, which is invaluable when components are pulled into live copy structures. Examining the rendered HTML in the browser's developer tools reveals the actual resource types and class names that Granite UI applies, which is often the fastest way to understand why a field is not behaving as expected. Accessibility-conscious teams should also fold WCAG checks into this iteration loop, and a practical starting point is the workflow outlined in AEM accessibility auditing with axe and WCAG 2.1 compliance.

Component inheritance through sling:resourceSuperType is another powerful technique. A base component can define a default dialog, and child components can extend or override specific tabs. This keeps the dialog configuration DRY across large component libraries and makes site-specific variations straightforward. Many Australian agencies use this pattern to deliver white-label experiences for clients without duplicating configuration files.

Iterative refinement is the final piece. Authors provide feedback on wording, field order, and defaults. Developers adjust the dialog, redeploy, and confirm that the authoring experience improves. Cycling through this loop a few times before locking a component down is rarely wasted effort, and it tends to surface edge cases that the original specification never anticipated.

Ready to put these techniques into practice on your next AEM build? Subscribe to the CIRCUIT newsletter for more hands-on guidance from the Adobe developer community, and explore the full archive of technical sessions to deepen your craft.