AEM Component Creation: Complete Guide for Developers
Introduction to Adobe Experience Manager Components
Adobe Experience Manager (AEM) has established itself as the leading enterprise content management system for building sophisticated digital experiences across web, mobile, and emerging channels. At the heart of AEM’s flexibility and power lies its component-based architecture, enabling developers to create reusable, modular building blocks that content authors assemble into compelling web pages and digital experiences.
Component creation represents a fundamental skill for AEM developers, forming the foundation upon which entire digital experiences are built. Whether you’re developing simple text components or complex, interactive modules with dynamic behavior, understanding component architecture, development patterns, and best practices is essential for creating maintainable, performant, and author-friendly solutions.
This comprehensive guide explores AEM component creation from fundamental concepts through advanced implementation patterns. We’ll examine component structure, dialog configuration, HTL templating, Sling Models, client libraries, and testing strategies. Whether you’re new to AEM development or seeking to deepen your expertise, this article provides practical guidance for building professional-grade AEM components that delight content authors and deliver exceptional digital experiences.
Understanding AEM Component Architecture
What Are AEM Components?
AEM components represent reusable content modules that authors can place on pages, configure through dialogs, and arrange to create complete digital experiences. Each component encapsulates specific functionality, from simple text display to complex integrations with external systems, providing consistent behavior and appearance across the website.
Components follow a modular architecture where each component is self-contained with its own rendering logic, styling, and client-side behavior. This modularity enables component reuse across multiple pages and sites, promotes consistency, and simplifies maintenance by centralizing component logic in single locations.
The component paradigm empowers content authors without technical expertise to create sophisticated pages by dragging and dropping components, configuring properties through intuitive dialogs, and arranging components visually. This separation between development and content creation accelerates content production while maintaining technical quality and brand consistency.
AEM components inherit from parent components through the Sling resource type hierarchy, enabling component extension and customization without duplicating code. This inheritance mechanism supports creating component families sharing common functionality while allowing specialized variants for specific use cases.
Component Structure and File Organization
AEM components follow a standardized directory structure organizing various component artifacts logically. Understanding this structure is fundamental to component development and maintenance.
Components reside within the /apps directory in the JCR repository, organized by project and component category. A typical component path follows the pattern /apps/[project-name]/components/content/[component-name]. This organization separates project-specific components from AEM core components and third-party components.
Each component directory contains several key files defining component behavior and appearance. The .content.xml file defines component metadata including title, description, component group, and resource type. This metadata controls how the component appears in the component browser and its inheritance relationships.
The component’s dialog, typically named cq:dialog, defines the authoring interface presenting form fields to content authors for configuring component properties. Dialog definitions use Granite UI framework creating modern, responsive authoring experiences.
HTML Template Language (HTL) files, typically named after the component or using specific names like main.html or default.html, contain the component’s rendering logic and markup. HTL combines HTML with dynamic expressions that access component properties and execute business logic.
Java classes, particularly Sling Models, implement component business logic, data transformation, and integration with AEM services and external systems. These classes are referenced from HTL templates through the data-sly-use attribute.
Client-side libraries (clientlibs) containing CSS, JavaScript, and other assets reside in dedicated directories within or alongside the component. These clientlibs provide component styling and interactive behavior.
Component Types and Categories
AEM distinguishes several component types serving different purposes within the content authoring workflow. Understanding these types guides appropriate component design and implementation.
Content components represent the primary building blocks authors use to create page content. Examples include text, image, teaser, carousel, and form components. These components typically render visible content and provide rich authoring interfaces for content configuration.
Container components provide layout structures organizing other components. Examples include responsive grids, tabs, accordions, and custom layout containers. Container components manage child component arrangement and responsive behavior without necessarily rendering their own content.
Page components define the overall page structure including header, footer, and main content areas. Page components typically inherit from AEM’s core page component extending it with project-specific functionality.
Experience Fragment components enable reusing content blocks across multiple pages and channels. These specialized components facilitate consistent content delivery across touchpoints.
Form components capture user input including text fields, dropdowns, checkboxes, and submit buttons. These components integrate with AEM Forms or custom form handling logic.
Functional components provide utility features like breadcrumbs, navigation, search, and social sharing. These components enhance site usability without directly containing authored content.
Sling Resource Types and Super Types
Sling resource types represent the foundation of AEM’s component system, defining component identity and enabling inheritance hierarchies. Understanding resource types is crucial for effective component development.
Every component defines a resource type represented by its path in the JCR repository. For example, a hero component might have resource type myproject/components/content/hero. This resource type uniquely identifies the component within the system.
The super type property (sling:resourceSuperType) defines component inheritance, allowing components to extend existing components inheriting their functionality, rendering logic, and configuration. Component hierarchies enable creating specialized variants without duplicating code.
When AEM renders a component, it searches for rendering scripts starting with the component’s resource type and proceeding up the super type hierarchy. This resolution mechanism allows child components to override specific aspects of parent components while inheriting other behaviors.
Inheritance from AEM Core Components represents a common pattern leveraging Adobe’s maintained, accessible, and performant component implementations. Projects extend core components adding project-specific functionality and styling while benefiting from Adobe’s ongoing improvements.
Proxy components provide an intermediate layer between project implementations and core components, enabling version control and isolation from direct core component dependencies. This pattern protects projects from breaking changes in core component updates.
Creating Your First AEM Component
Setting Up the Development Environment
Effective AEM component development requires properly configured development environments providing necessary tools and accelerating development workflows.
Install AEM SDK including the Quickstart JAR for local author and publish instances. The SDK provides complete AEM functionality locally enabling development, testing, and debugging without requiring remote server access. Configure separate author and publish instances matching the production architecture.
Set up Maven as the build tool managing project dependencies, compilation, packaging, and deployment. AEM projects follow Maven conventions with standard directory structures and lifecycle phases. Configure Maven settings referencing Adobe’s Maven repositories for accessing AEM dependencies.
Configure integrated development environments like IntelliJ IDEA, Eclipse, or Visual Studio Code with AEM-specific plugins. These plugins provide syntax highlighting for HTL, debugging support, and integration with local AEM instances. The Adobe Developer Tools enhance productivity through features like content synchronization and component scaffolding.
Install frontend build tools including Node.js and npm for managing client-side dependencies and build processes. Modern AEM projects often use webpack, Gulp, or other build tools compiling and optimizing frontend assets.
Set up version control using Git managing component code, configuration, and related artifacts. Initialize repositories with appropriate .gitignore files excluding compiled artifacts and local configuration.
Configure content package management tools like Vault CLI or the AEM Developer Chrome extension facilitating content synchronization between the repository and local filesystem. These tools streamline development workflows enabling file-based editing with automatic synchronization.
Component Folder Structure
Creating well-organized component directories following AEM conventions ensures maintainability and team collaboration.
Navigate to /apps/[project-name]/components/content/ in CRXDE Lite or your local development environment. This location houses project content components distinct from page components and other component types.
Create a new folder named after your component using lowercase with hyphens separating words (e.g., feature-banner). This naming convention maintains consistency and readability.
Within the component folder, create the .content.xml file defining component metadata. This XML file must specify the jcr:primaryType as cq:Component and include properties like jcr:title, jcr:description, and componentGroup.
Example .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:Component"
jcr:title="Feature Banner"
jcr:description="A prominent banner highlighting key features"
componentGroup="MyProject - Content"/>Create the main HTL file (e.g., featurebanner.html) containing the component’s rendering template. This file combines HTML markup with HTL expressions accessing component properties and business logic.
Add a cq:dialog folder containing the component’s authoring dialog definition. This dialog provides the interface through which content authors configure component properties.
Optionally create additional folders for clientlibs, policies, and design dialogs as component complexity requires. This organization keeps related artifacts together while maintaining clear separation of concerns.
Creating the Component Dialog
The component dialog defines the authoring interface enabling content authors to configure component properties. Dialogs use the Granite UI framework providing rich, accessible form fields.
Create a cq:dialog folder within your component directory. This folder contains the dialog definition in a .content.xml file.
Define dialog structure using nested XML nodes representing tabs, fieldsets, and individual form fields. The dialog root node specifies sling:resourceType="cq:Dialog" indicating it’s a component dialog.
Example dialog structure:
<?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"
xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
jcr:primaryType="nt:unstructured"
jcr:title="Feature Banner Properties"
sling:resourceType="cq:Dialog">
<content
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/container">
<items jcr:primaryType="nt:unstructured">
<tabs
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/tabs">
<items jcr:primaryType="nt:unstructured">
<basic
jcr:primaryType="nt:unstructured"
jcr:title="Basic"
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"/>
<description
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textarea"
fieldLabel="Description"
name="./description"/>
</items>
</basic>
</items>
</tabs>
</items>
</content>
</jcr:root>Add various field types based on authoring requirements including text fields, textareas, pathfields for asset selection, checkboxes for boolean values, select dropdowns for predefined options, and multifield for repeating field groups.
Implement field validation through validator properties ensuring authors provide required information in appropriate formats. Validation properties include required="true", pattern matching with regular expressions, and custom validators.
Organize complex dialogs using tabs grouping related fields logically. Tabs improve authoring experience by categorizing properties preventing overwhelming single-screen dialogs.
Writing HTL Templates
HTML Template Language (HTL) serves as AEM’s primary templating language combining HTML with dynamic expressions. HTL provides secure, performant rendering while maintaining clear separation between markup and logic.
Create the component’s HTL file within the component directory. The filename typically matches the component name or uses generic names like main.html.
Access component properties using the properties object with dot notation. Expression syntax ${properties.title} retrieves the property named “title” from the component’s authored data.
Example basic HTL template:
<div class="feature-banner">
<h2 class="feature-banner__title">${properties.title}</h2>
<p class="feature-banner__description">${properties.description}</p>
</div>Use HTL block statements for conditional rendering and iteration. The data-sly-test attribute conditionally includes elements based on expression evaluation. The data-sly-list attribute iterates over collections rendering repeated content.
Example with conditionals:
<div class="feature-banner" data-sly-test="${properties.title}">
<h2 class="feature-banner__title">${properties.title}</h2>
<p class="feature-banner__description" data-sly-test="${properties.description}">
${properties.description}
</p>
</div>Integrate Sling Models using data-sly-use attribute instantiating Java classes providing business logic and data processing. The use statement creates model instances accessible within the template.
<div data-sly-use.model="com.myproject.core.models.FeatureBanner">
<h2>${model.title}</h2>
<p>${model.formattedDescription}</p>
</div>Apply HTL display contexts ensuring proper escaping and security. Display contexts automatically escape content preventing XSS vulnerabilities. Common contexts include text, html, attribute, uri, and number.
Include other components or snippets using data-sly-include or data-sly-resource enabling component composition and reusability.
Advanced Component Development Patterns
Creating Sling Models
Sling Models provide elegant approach to mapping component resources to Java objects, implementing business logic, and providing clean APIs for HTL templates. Models separate concerns between presentation and business logic.
Create a Java class annotated with @Model specifying adaptation sources. Models typically adapt from SlingHttpServletRequest or Resource depending on whether request context is needed.
package com.myproject.core.models;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;
@Model(adaptables = SlingHttpServletRequest.class)
public class FeatureBanner {
@ValueMapValue
private String title;
@ValueMapValue
private String description;
public String getTitle() {
return title;
}
public String getDescription() {
return description;
}
public String getFormattedDescription() {
if (description != null && description.length() > 100) {
return description.substring(0, 100) + "...";
}
return description;
}
}Use injection annotations for automatic property mapping from component resources. @ValueMapValue injects properties from the component’s ValueMap. @ChildResource injects child resources. @OSGiService injects OSGi services.
Implement default interfaces like @DefaultInjectionStrategy(DefaultInjectionStrategy.OPTIONAL) controlling injection behavior and handling missing properties gracefully.
Add business logic methods transforming raw properties into presentation-ready data. Examples include formatting dates, truncating text, generating URLs, and aggregating data from multiple sources.
Leverage constructor injection for required dependencies improving testability and making dependencies explicit. Constructor injection works well with unit testing frameworks allowing mock injection.
@Model(adaptables = SlingHttpServletRequest.class)
public class FeatureBanner {
private final ResourceResolver resourceResolver;
private final String title;
@Inject
public FeatureBanner(
ResourceResolver resourceResolver,
@ValueMapValue(name = "title") String title) {
this.resourceResolver = resourceResolver;
this.title = title;
}
public String getTitle() {
return title;
}
}Implement @PostConstruct methods for initialization logic executing after injection completes. Use this for complex initialization, validation, or data loading that shouldn’t occur in constructors.
Also Read: Adobe Tutorial
Multi-field and Composite Multi-field
Multi-fields enable authors to add multiple instances of field groups creating dynamic, variable-length content collections. This pattern suits components displaying lists, carousels, or any repeating content.
Implement multi-field using Granite UI’s multifield component wrapping desired field definition. The multifield stores data as child nodes or as JSON in a single property depending on configuration.
Simple multifield example storing multiple text values:
<items
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/multifield"
fieldLabel="Features">
<field
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
name="./features"/>
</multifield>Composite multifields group multiple fields enabling complex data structures. Each multifield item contains multiple properties like titles, descriptions, and images.
<items
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/multifield"
composite="{Boolean}true"
fieldLabel="Feature Items">
<field
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/container"
name="./items">
<items jcr:primaryType="nt:unstructured">
<title
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="Title"
name="title"/>
<description
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textarea"
fieldLabel="Description"
name="description"/>
</items>
</field>
</items>Process multifield data in Sling Models iterating over child resources or parsing JSON arrays depending on storage format. The model exposes structured data to HTL templates.
@Model(adaptables = Resource.class)
public class FeatureList {
@ChildResource
private List<Resource> items;
public List<FeatureItem> getItems() {
if (items == null) return Collections.emptyList();
return items.stream()
.map(resource -> resource.adaptTo(FeatureItem.class))
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
}
@Model(adaptables = Resource.class)
public class FeatureItem {
@ValueMapValue
private String title;
@ValueMapValue
private String description;
public String getTitle() {
return title;
}
public String getDescription() {
return description;
}
}Render multifield items in HTL using list iteration displaying each item according to component design.
<div class="feature-list" data-sly-use.model="com.myproject.core.models.FeatureList">
<ul>
<li data-sly-repeat.item="${model.items}">
<h3>${item.title}</h3>
<p>${item.description}</p>
</li>
</ul>
</div>Working with Assets and DAM
Many components integrate with AEM’s Digital Asset Management (DAM) system displaying images, videos, documents, and other assets. Implementing proper DAM integration provides authors with asset selection capabilities and optimized asset delivery.
Use pathfield or pathbrowser fields in dialogs enabling authors to select assets from DAM. Configure root paths and filters constraining selection to appropriate asset types.
<image
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/pathfield"
fieldLabel="Image"
name="./imagePath"
rootPath="/content/dam/myproject"/>Access selected assets in Sling Models retrieving asset resources and extracting metadata. The Asset API provides rich functionality for working with DAM assets.
@Model(adaptables = Resource.class)
public class ImageComponent {
@ValueMapValue
private String imagePath;
@OSGiService
private ResourceResolver resourceResolver;
public String getImageUrl() {
if (imagePath == null) return null;
Resource assetResource = resourceResolver.getResource(imagePath);
if (assetResource != null) {
Asset asset = assetResource.adaptTo(Asset.class);
if (asset != null) {
return asset.getPath();
}
}
return null;
}
public String getAltText() {
Resource assetResource = resourceResolver.getResource(imagePath);
if (assetResource != null) {
Asset asset = assetResource.adaptTo(Asset.class);
if (asset != null) {
return asset.getMetadataValue("dc:title");
}
}
return "";
}
}Implement responsive image rendering using AEM’s adaptive image servlet generating appropriately sized renditions. This optimization improves page load performance delivering images matching display requirements.
<div class="image-component" data-sly-use.model="com.myproject.core.models.ImageComponent">
<img src="${model.imageUrl}"
alt="${model.altText}"
loading="lazy"/>
</div>Consider using Core Components’ Image component as a foundation inheriting its sophisticated features including responsive images, smart cropping, and lazy loading. Extend through delegation or inheritance adding project-specific functionality.
Component Policies and Design Dialogs
Component policies enable template authors to configure default values and constraints for components used on templates. Policies separate template-level configuration from page-level authoring.
Create design dialogs defining configurable policies using similar structure to component dialogs but stored in cq:design_dialog nodes.
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
xmlns:jcr="http://www.jcp.org/jcr/1.0"
xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
jcr:primaryType="nt:unstructured"
jcr:title="Feature Banner Policy"
sling:resourceType="cq:Dialog">
<content jcr:primaryType="nt:unstructured">
<items jcr:primaryType="nt:unstructured">
<defaultStyle
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/select"
fieldLabel="Default Style"
name="./defaultStyle">
<items jcr:primaryType="nt:unstructured">
<default
jcr:primaryType="nt:unstructured"
text="Default"
value="default"/>
<prominent
jcr:primaryType="nt:unstructured"
text="Prominent"
value="prominent"/>
</items>
</defaultStyle>
</items>
</content>
</jcr:root>Access policy properties in Sling Models using @InjectionStrategy with OPTIONAL strategy and @Source annotation specifying policy injection.
@Model(adaptables = SlingHttpServletRequest.class)
public class FeatureBanner {
@ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL)
@Source("policy")
private String defaultStyle;
public String getStyle() {
// Use policy value as default, allow component property to override
String componentStyle = properties.get("style", String.class);
return componentStyle != null ? componentStyle : defaultStyle;
}
}Template authors configure policies through the template editor’s policy dialog. These configurations apply to all component instances on pages using that template unless overridden at the component level.
Use policies for template-level configurations like allowed components in containers, default values reducing authoring effort, styling options ensuring brand consistency, and resource constraints like maximum items in lists.
Client-Side Development for Components
Creating Component Client Libraries
Client libraries (clientlibs) package CSS, JavaScript, and other frontend assets for components. Properly structured clientlibs optimize asset delivery and manage dependencies.
Create a clientlibs folder within your component directory. This folder contains all frontend assets and configuration for the component’s clientlib.
Define clientlib properties in a .content.xml file specifying the clientlib category, dependencies, and inclusion behavior.
<?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="[myproject.components.featurebanner]"
dependencies="[myproject.base]"/>Organize CSS files within a css or less/scss folder depending on preprocessing requirements. Create a css.txt file listing CSS files in load order.
Example css.txt:
featurebanner.cssOrganize JavaScript files within a js folder. Create a js.txt file listing JavaScript files in load order ensuring dependencies load before dependent code.
Example js.txt:
featurebanner.jsImplement JavaScript following best practices including defensive coding checking for element existence, event delegation for dynamic content, and namespace patterns avoiding global scope pollution.
(function($, document) {
'use strict';
var SELECTOR = '.feature-banner';
function init() {
$(SELECTOR).each(function() {
var $banner = $(this);
// Component initialization logic
});
}
$(document).ready(init);
})(jQuery, document);Include clientlibs in page components using data-sly-call="${clientlib.css @ categories='myproject.components'}" for CSS and similar for JavaScript. The clientlib HTL library handles proper inclusion with caching and minification.
Component Styling with BEM
Block Element Modifier (BEM) methodology provides consistent, maintainable CSS architecture particularly suited to component-based development. BEM creates clear naming conventions preventing style conflicts and improving code clarity.
Structure CSS classes following BEM naming: .block__element--modifier pattern. The block represents the component, elements are parts of the component, and modifiers represent variations or states.
Example BEM structure:
.feature-banner {
padding: 2rem;
background: #f5f5f5;
}
.feature-banner__title {
font-size: 2rem;
margin-bottom: 1rem;
}
.feature-banner__description {
font-size: 1rem;
line-height: 1.5;
}
.feature-banner--prominent {
background: #333;
color: #fff;
}
.feature-banner--prominent .feature-banner__title {
color: #ffd700;
}Apply BEM classes in HTL templates maintaining naming consistency:
<div class="feature-banner feature-banner--${model.style}">
<h2 class="feature-banner__title">${model.title}</h2>
<p class="feature-banner__description">${model.description}</p>
</div>Use CSS preprocessors like LESS or SASS organizing styles hierarchically while compiling to flat BEM structures. Preprocessors enable variables, mixins, and nesting improving stylesheet maintainability.
.feature-banner {
$block: &;
padding: 2rem;
&__title {
font-size: 2rem;
}
&__description {
font-size: 1rem;
}
&--prominent {
background: #333;
#{$block}__title {
color: #ffd700;
}
}
}JavaScript Patterns and Best Practices
Writing maintainable, performant JavaScript for AEM components requires following established patterns and best practices appropriate for content-managed environments.
Implement defensive coding checking for element existence before manipulation preventing errors when components aren’t present on pages:
(function() {
'use strict';
var banners = document.querySelectorAll('.feature-banner');
if (!banners || banners.length === 0) {
return; // Exit early if no banners exist
}
banners.forEach(function(banner) {
// Initialize each banner instance
initBanner(banner);
});
function initBanner(element) {
var button = element.querySelector('.feature-banner__button');
if (button) {
button.addEventListener('click', handleClick);
}
}
function handleClick(event) {
event.preventDefault();
// Handle button click
}
})();Use event delegation for dynamic content enabling event handling on elements added after initial page load:
document.addEventListener('click', function(event) {
if (event.target.matches('.feature-banner__button')) {
// Handle clicks on banner buttons
handleButtonClick(event);
}
});Implement author/publish mode detection adjusting behavior appropriately. Components may need different behavior in edit mode versus published pages:
var isEditMode = (typeof Granite !== 'undefined') &&
Granite.author &&
Granite.author.MessageChannel;
if (!isEditMode) {
// Initialize interactive features only on publish
initInteractiveFeatures();
}Consider using modern JavaScript frameworks like React or Vue for complex interactive components. AEM supports SPA integration enabling rich client-side experiences while maintaining AEM’s authoring capabilities.
Responsive Design Considerations
Modern components must adapt seamlessly across devices and screen sizes. Implementing responsive design ensures optimal experiences regardless of viewport dimensions.
Use CSS media queries adapting component layout and styling based on viewport width:
.feature-banner {
padding: 1rem;
}
.feature-banner__title {
font-size: 1.5rem;
}
@media (min-width: 768px) {
.feature-banner {
padding: 2rem;
}
.feature-banner__title {
font-size: 2rem;
}
}
@media (min-width: 1024px) {
.feature-banner {
padding: 3rem;
}
.feature-banner__title {
font-size: 2.5rem;
}
}Implement mobile-first approaches styling base mobile experience first then enhancing for larger viewports. This methodology ensures solid mobile foundations with progressive enhancement.
Use flexible layouts with relative units (percentages, em, rem) rather than fixed pixels enabling fluid adaptation to different screen sizes:
.feature-banner {
width: 100%;
max-width: 1200px;
margin: 0 auto;
padding: 2rem 5%; /* Relative padding */
}Leverage AEM’s responsive grid system for layout components providing authors with responsive breakpoint controls. The responsive grid enables visual breakpoint management without custom development.
Test components across devices and browsers ensuring consistent experiences. Use browser developer tools’ device emulation, physical devices, and automated testing tools validating responsive behavior.
Testing AEM Components
Unit Testing with JUnit and Mockito
Comprehensive unit testing ensures component reliability, facilitates refactoring, and documents expected behavior. AEM components benefit from thorough testing of Sling Models and backend logic.
Set up test dependencies in Maven including JUnit, Mockito, and AEM-specific testing utilities:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.wcm</groupId>
<artifactId>io.wcm.testing.aem-mock.junit4</artifact