> For AI agents: the complete documentation index is available at https://nhanbe.id.vn/llms.txt, the full documentation bundle is available at https://nhanbe.id.vn/llms-full.txt.

# HTL Templates

## HTL (HTML Template Language) in AEM

HTL (formerly Sightly) is AEM's server-side template language. It completely replaces JSP and serves as the standard for rendering components. HTL's core philosophy is **secure by default** — automatically escaping output data to prevent XSS attacks.

***

## 1. Expressions

Expressions print values to HTML using `$\{\}` syntax:

```html
<!-- Print JCR property directly -->
<h1>${properties.jcr:title}</h1>

<!-- Fetch data from Sling Model -->
<p>${model.description}</p>

<!-- Literal string -->
<span>${'Hello, World!'}</span>
```

### 1.1. Expression Options

Modify how data is rendered by adding `@` following the variable:

```html
<!-- Context (Specify security context) -->
<a href="${link.url @ context='uri'}">${link.text}</a>

<!-- Format (String formatting) -->
<p>${'Price: {0}' @ format=product.price}</p>

<!-- i18n (Internationalization translation) -->
<span>${'Read More' @ i18n}</span>

<!-- Join (Join array elements) -->
<p>${model.tags @ join=', '}</p>
```

### 1.2. Display Contexts

Although HTL chooses security context automatically, you can (and sometimes must) override it. Common contexts include:

| Context            | Usage Location                | HTL Example                                               |
| ------------------ | ----------------------------- | --------------------------------------------------------- |
| `html` _(default)_ | Safe HTML content             | `&lt;p&gt;$\{text\}&lt;/p&gt;`                            |
| `text`             | Plain text (Strips HTML)      | `&lt;title&gt;$\{title @ context='text'\}&lt;/title&gt;`  |
| `attribute`        | HTML tag attribute            | `&lt;div class="$\{cssClass @ context='attribute'\}"&gt;` |
| `uri`              | URL path                      | `&lt;a href="$\{url @ context='uri'\}"&gt;`               |
| `scriptString`     | Assign to JavaScript variable | `var x = "$\{val @ context='scriptString'\}"`             |
| `unsafe`           | Bypass security               | `$\{richText @ context='unsafe'\}`                        |

> **Security Warning:** NEVER use `context='unsafe'` unless completely certain content is safe. For Rich Text Editor (RTE) output, RTE sanitizes malicious code on save, and default `html` context provides extra protection on render. RTE output called via `$\{model.richText\}` is safe!

***

## 2. Block Statements

Block statements are HTML attributes prefixed with `data-sly-`. They control HTL logic execution.

### 2.1. `data-sly-use` (Initialize Model)

Loads a Sling Model or template file into a variable.

```html
<!-- Load Sling Model -->
<div data-sly-use.model="com.mysite.core.models.ArticleModel">
    <h1>${model.title}</h1>
</div>

<!-- Load another HTL file -->
<div data-sly-use.template="partials/header.html"></div>

<!-- Pass parameters into Model -->
<div data-sly-use.model="${'com.mysite.core.models.ListModel' @ limit=5}"></div>
```

_(Variable identifier follows dot — e.g. `.model` — used in HTL)._

### 2.2. `data-sly-test` (Conditional Rendering)

Functions like an `if` statement. Can also be used for variable assignment.

```html
<!-- Render <h1> only if title exists -->
<h1 data-sly-test="${model.title}">${model.title}</h1>

<!-- If/Else pattern with variable assignment (.hasItems) -->
<div data-sly-test.hasItems="${model.items.size > 0}">
    <p>Found ${model.items.size} results</p>
</div>
<div data-sly-test="${!hasItems}">
    <p>No results found.</p>
</div>
```

_Note: Values evaluated as `false`: null, empty string `""`, number `0`, boolean `false`, and empty collections/arrays._

### 2.3. `data-sly-list` vs `data-sly-repeat` (Loops)

Both iterate over arrays/collections, but impact DOM structure differently.

```mermaid
graph TD
    Data[Array: 'Apple', 'Banana'] --> List
    Data --> Repeat

    subgraph data-sly-list
        List["ul (data-sly-list.item=…)"]
        List --> L1["ul"]
        L1 --> L2["li: Apple"]
        L1 --> L3["li: Banana"]
        L1 --> L4["/ul"]
    end

    subgraph data-sly-repeat
        Repeat["div (data-sly-repeat.item=… class='card')"]
        Repeat --> R1["div.card: Apple"]
        Repeat --> R2["div.card: Banana"]
    end
```

**Loop Helper Variable (`itemList`):** Inside loops, HTL automatically generates `itemList` object with properties:

| Variable                  | Function                               |
| ------------------------- | -------------------------------------- |
| `itemList.index`          | Current index (0-based)                |
| `itemList.count`          | Current count (1-based)                |
| `itemList.first` / `last` | Returns `true` if first / last element |
| `itemList.middle`         | Returns `true` if middle element       |
| `itemList.odd` / `even`   | Returns `true` if index is odd / even  |

```html
<ul data-sly-list.item="${model.articles}">
    <li>${item.title} - Viewing item ${itemList.count}</li>
</ul>
```

### 2.4. `data-sly-resource` vs `data-sly-include`

Embeds another component or HTML file into current template.

- **`data-sly-resource`:** Embeds a component. Triggers Sling Resolution (invokes Java logic, loads child component Dialog).

  ```html
  <!-- Call component with resourceType -->
  <div data-sly-resource="${'header' @ resourceType='mysite/components/header'}"></div>

  <!-- Call with selector -->
  <div data-sly-resource="${'sidebar' @ resourceType='mysite/components/sidebar', selectors='compact'}"></div>

  <!-- Change wrapper tag of child component -->
  <div data-sly-resource="${'text' @ resourceType='mysite/components/text', decorationTagName='article'}"></div>
  ```

- **`data-sly-include`:** Includes a static HTML snippet directly. Does **NOT** trigger Sling Resolution.

  ```html
  <div data-sly-include="partials/footer.html"></div>
  <div data-sly-include="/apps/mysite/components/shared/utils.html"></div>
  ```

### 2.5. `data-sly-template` / `data-sly-call` (UI Reuse)

Defines an HTML block as a function and calls it in multiple places.

```html
<!-- Define template -->
<template data-sly-template.card="${@ title, description, link}">
    <div class="card">
        <h3>${title}</h3>
        <p>${description}</p>
        <a href="${link @ context='uri'}">Read More</a>
    </div>
</template>

<!-- Call template with arguments -->
<div data-sly-list="${model.articles}">
    <div data-sly-call="${card @ title=item.title, description=item.excerpt, link=item.path}"></div>
</div>
```

### 2.6. `data-sly-element` (Dynamic HTML Tag)

Dynamically changes HTML element based on configuration.

```html
<!-- If model.headingLevel is 'h2', tag renders as <h2> -->
<h1 data-sly-element="${model.headingLevel || 'h1'}">${model.title}</h1>
```

### 2.7. `data-sly-attribute` (HTML Attributes)

```html
<!-- Set single attribute -->
<div data-sly-attribute.id="${model.anchorId}">Content</div>

<!-- Set multiple attributes via Map -->
<div data-sly-attribute="${model.attributes}">Content</div>

<!-- Conditional attribute (removed automatically if empty/false) -->
<input data-sly-attribute.disabled="${model.isDisabled ? 'disabled' : ''}"/>
```

### 2.8. `data-sly-text` (Override Text)

Retains mockup text for frontend design time, automatically replaced when rendered.

```html
<p data-sly-text="${model.description}">Mock text replaced at runtime.</p>
```

### 2.9. `data-sly-unwrap` (Remove Wrapper Tag)

Keeps inner content while unwrapping host HTML tag from DOM. Useful for logical wrappers without introducing unnecessary wrapper `&lt;div/&gt;` elements.

```html
<div data-sly-unwrap>
    <p>This paragraph will not be wrapped in div in browser DOM.</p>
</div>
```

### 2.10. `data-sly-set` (Local Variable Assignment)

Introduced in HTL 1.4, assigns calculated values to local variables without creating HTML tags.

```html
<sly data-sly-set.fullName="${model.firstName} ${model.lastName}"/>
<p>Hello, ${fullName}</p>
```

***

## 3. Global Objects

HTL templates automatically inject global objects:

| Object                    | Type                       | Description                                      |
| ------------------------- | -------------------------- | ------------------------------------------------ |
| `properties`              | `ValueMap`                 | Properties of current resource (JCR node).       |
| `pageProperties`          | `ValueMap`                 | Properties of current page (`jcr:content` node). |
| `inheritedPageProperties` | `ValueMap`                 | Inherited properties from ancestor pages.        |
| `currentPage`             | `Page`                     | Current AEM Page object.                         |
| `currentNode`             | `Node`                     | Current JCR Node object.                         |
| `resource`                | `Resource`                 | Current Sling Resource object.                   |
| `request`                 | `SlingHttpServletRequest`  | Current HTTP request object.                     |
| `response`                | `SlingHttpServletResponse` | Current HTTP response object.                    |
| `log`                     | `Logger`                   | Output log to AEM error.log.                     |
| `wcmmode`                 | `WCMMode`                  | Current UI mode (edit, preview, disabled).       |
| `currentStyle`            | `Style`                    | Current component Policy configuration.          |

**Practical Example:**

```html
<!-- Get page title -->
<title>${currentPage.title || currentPage.name}</title>

<!-- Check Edit mode to render placeholder for authors -->
<div data-sly-test="${wcmmode.edit || wcmmode.preview}">
    <p>Component is empty. Drag and drop data here.</p>
</div>

<!-- Print debug log -->
${'Component rendering started' @ log}
```

***

## 4. The Use API

3 ways to connect HTL with Backend Logic:

### Level 1: Call Sling Model Directly (Recommended)

Standard approach for 99% of components.

```html
<div data-sly-use.model="com.mysite.core.models.ArticleModel">
    ${model.title}
</div>
```

### Level 2: Call Sling Model with Parameters

Pass parameters from HTL to Java model.

**HTL Template:**

```html
<div data-sly-use.pagination="${'com.mysite.core.models.PaginationModel' @ currentPage=3, totalPages=10}">
    Page ${pagination.currentPage} / ${pagination.totalPages}
</div>
```

**Java Sling Model:**

```java
@Model(adaptables = SlingHttpServletRequest.class)
public class PaginationModel {
    @RequestAttribute
    private int currentPage;

    @RequestAttribute
    private int totalPages;
}
```

### Level 3: Use Javascript Use Objects (Deprecated)

Server-side JS files. Deprecated on AEM as a Cloud Service (AEMaaCS). Always use Sling Models.

***

## 5. Practical Example: Article Card Component

```html
<!-- file: article-card.html -->

<!-- 1. Define reusable template -->
<template data-sly-template.articleCard="${@ article}">

    <!-- 2. Verify article object is not null -->
    <article class="cmp-article-card" data-sly-test="${article}">

        <!-- Render image if available -->
        <div data-sly-test="${article.image}" class="cmp-article-card__image">
            <img src="${article.image @ context='uri'}" alt="${article.imageAlt}" loading="lazy"/>
        </div>

        <div class="cmp-article-card__content">
            <!-- 3. Dynamic Element: Render h2, h3, or h4 -->
            <h3 data-sly-element="${article.headingLevel || 'h3'}" class="cmp-article-card__title">
                <a href="${article.link @ context='uri'}">${article.title}</a>
            </h3>

            <p data-sly-test="${article.excerpt}" class="cmp-article-card__excerpt">
                ${article.excerpt}
            </p>

            <span class="cmp-article-card__date">${article.formattedDate}</span>

            <!-- 4. Repeat loop for Tags -->
            <ul data-sly-test="${article.tags}" class="cmp-article-card__tags">
                <li data-sly-repeat="${article.tags}" class="cmp-article-card__tag">
                    ${item}
                </li>
            </ul>
        </div>
    </article>
</template>

<!-- Call Java model and pass model to template -->
<div data-sly-use.model="com.mysite.core.models.ArticleCardModel"
     data-sly-call="${articleCard @ article=model}">
</div>
```

***

## 6. HTL Best Practices

1. **Always Use Sling Models:** Keep complex logic in Java; HTL should only handle UI rendering.
2. **Never Use `context='unsafe'`:** Unless fully aware of security implications.
3. **Keep Templates Simple:** If conditional statements become complex, move logic to Java.
4. **Use Template & Call:** Split UI into reusable fragments (`data-sly-template`/`data-sly-call`).
5. **Follow BEM Naming:** Use `cmp-` prefix for CSS classes to align with Core Components standards.
6. **Provide Placeholders:** Use `wcmmode.edit` to show placeholder messages when component is unconfigured.
7. **Guard with `data-sly-test`:** Check variables for `null` before rendering markup.
