Your First Component
Component Structure and Building Your First Component in AEM
Components are the primary building blocks of AEM pages. A button, a text block, a carousel, or a hero banner — each of these elements is a component. In this article, we will build a component from scratch and understand every file involved.
1. The Component Triad
Every complete AEM Component is typically made up of three main parts (the triad). Not every component requires all three (an extremely simple component might only need HTL), but most real-world components use this architecture:
2. Project File Structure
A Component's source code is split across two separate locations (modules): UI (Frontend/Config) and Logic (Backend).
UI Module (Inside ui.apps):
ui.apps/src/main/content/jcr_root/apps/mysite/components/
└── hello/
├── .content.xml # Component definition (cq:Component definition)
├── hello.html # HTL template (Display markup)
└── _cq_dialog/
└── .content.xml # Author dialog (Authoring UI)
Backend Module (Inside core):
core/src/main/java/com/mysite/core/models/
└── HelloModel.java # Sling Model
3. Steps to Build a Component
Step 1: Component Definition
The .content.xml file in the component root folder defines the cq:Component node:
<?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="Hello"
jcr:description="A simple greeting component"
componentGroup="My Site - Content"/>
Step 2: Write HTL Template
HTL (HTML Template Language) handles rendering HTML. Create hello.html:
<div class="cmp-hello" data-sly-use.model="com.mysite.core.models.HelloModel">
<h2 class="cmp-hello__title">${model.greeting}</h2>
<p class="cmp-hello__message">${model.message}</p>
</div>
This template performs 3 actions:
- Uses
data-sly-use to instantiate the Sling Model (Java class).
- Retrieves data from the Model using
$\{model.property\} syntax.
- Outputs HTML adhering to BEM naming conventions (
cmp- prefix) — standard for AEM Core Components.
Step 3: Create Sling Model
The Sling Model reads values from the JCR node and prepares data for HTL:
package com.mysite.core.models;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.Default;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;
@Model(adaptables = Resource.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class HelloModel {
@ValueMapValue
@Default(values = "Hello, World!")
private String greeting;
@ValueMapValue
@Default(values = "This is my first AEM component.")
private String message;
public String getGreeting() { return greeting; }
public String getMessage() { return message; }
}
Annotation Explanations:
Important: If you omit defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL, Sling Model's default strategy is REQUIRED. If an author drags the component onto a page without entering form data (no JCR data yet), the model fails and throws an error instead of displaying defaults. Always use OPTIONAL.
Step 4: Create Author Dialog
The Dialog defines which input fields an author sees. Create _cq_dialog/.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" xmlns:nt="http://www.jcp.org/jcr/nt/1.0" xmlns:granite="http://www.adobe.com/jcr/granite/1.0" xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
jcr:primaryType="nt:unstructured"
jcr:title="Hello"
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">
<tabs jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/tabs" maximized="{Boolean}true">
<items jcr:primaryType="nt:unstructured">
<!-- Properties Tab -->
<properties jcr:primaryType="nt:unstructured" jcr:title="Properties" sling:resourceType="granite/ui/components/coral/foundation/container" margin="{Boolean}true">
<items jcr:primaryType="nt:unstructured">
<columns jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/fixedcolumns" margin="{Boolean}true">
<items jcr:primaryType="nt:unstructured">
<column jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/container">
<items jcr:primaryType="nt:unstructured">
<!-- Greeting Input Field -->
<greeting jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="Greeting"
name="./greeting"
required="{Boolean}true"/>
<!-- Message Input Field -->
<message jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textarea"
fieldLabel="Message"
name="./message"/>
</items>
</column>
</items>
</columns>
</items>
</properties>
</items>
</tabs>
</items>
</content>
</jcr:root>
Note: The name attribute (such as name="./greeting") is critical. It defines the property name saved in JCR and must match the variable injected in your Sling Model (private String greeting;).
4. Component Rendering Flow
What actually happens when a user loads a page containing your component?
5. Deployment and Testing
-
Build source code: In project root (mysite), run:
mvn clean install -PautoInstallSinglePackage
-
Drag & drop component onto a page:
- Open AEM:
http://localhost:4502/sites.html
- Edit a page (Edit mode).
- Open Side Panel, search for
Hello and drag it onto the page.
- Click component (wrench icon) to open Dialog, enter content and save.
-
Verify in CRXDE Lite: Browse to the page path, navigate to the component node you dropped, and verify stored data:
hello
├── jcr:primaryType = "nt:unstructured"
├── sling:resourceType = "mysite/components/hello"
├── greeting = "Welcome to My Site" (<- From Dialog)
└── message = "We build amazing things." (<- From Dialog)
6. Debug Playbook: From Dialog to HTML (4 Checkpoints)
If your component does not render content as expected, use this 4-step check to isolate the issue:
7. Proxy Components (AEM Core Components)
In real project scenarios, instead of building basic components like "Text" or "Image" from scratch, we use Core Components (provided out-of-the-box by Adobe) via the Proxy Component design pattern.
Create a lightweight component pointing to a Core Component via sling:resourceSuperType:
<!-- apps/mysite/components/text/.content.xml -->
<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="Text"
componentGroup="My Site - Content"
sling:resourceSuperType="core/wcm/components/text/v2/text"/>
Thanks to sling:resourceSuperType, your component inherits all features, dialogs, and Sling Models from core/wcm/components/text/v2/text. You only override specific files (such as HTL) if you want custom markup. The Maven archetype generates these proxies automatically when initializing a project.