# HTL Block Statements: The Complete data-sly-* Reference

In the previous post you wrote your first HTL script and used a handful of `data-sly-*` attributes without much explanation. HTL (HTML Template Language) has a small, well-defined set of these **block statements**, and knowing all of them will make your component markup cleaner and your debugging faster. This post is a complete reference with a working example for each.

## HTL in 60 Seconds

HTL is plain HTML plus two additions:

- **Expressions**: `${...}` outputs a value, with automatic, context-aware XSS protection
- **Block statements**: `data-sly-*` attributes that attach behavior (conditions, loops, includes) to any HTML element

Expressions can also carry **options** after an `@` sign, such as `${properties.text @ context='html'}`.

## Quick Reference

| Statement | Purpose |
| --- | --- |
| `data-sly-use` | Load a Sling Model, script or template library |
| `data-sly-test` | Conditional rendering |
| `data-sly-set` | Assign a variable |
| `data-sly-text` | Replace an element's text content |
| `data-sly-attribute` | Set or remove attributes dynamically |
| `data-sly-element` | Change an element's tag name dynamically |
| `data-sly-list` | Loop over the element's *content* |
| `data-sly-repeat` | Loop, repeating the *element itself* |
| `data-sly-resource` | Render another resource with its own component |
| `data-sly-include` | Include another script in the current context |
| `data-sly-template` | Define a reusable template |
| `data-sly-call` | Call a template |
| `data-sly-unwrap` | Remove the host element, keep its content |

There is also the special `<sly>` element, covered at the end.

## data-sly-use

Loads logic and binds it to an identifier. This is how HTL connects to your Sling Models.

```html
<div data-sly-use.hero="com.myproject.core.models.HeroBanner">
    ${hero.title}
</div>
```

The text after the dot (`hero`) is the variable name. You can pass parameters to the model or script:

```html
<div data-sly-use.card="${'com.myproject.core.models.Card' @ style='compact'}">
```

`data-sly-use` also loads HTL **template libraries** from other files, which pairs with `data-sly-call` (below):

```html
<sly data-sly-use.lib="templates/cards.html"></sly>
```

## data-sly-test

Renders the element only when the expression is truthy. Falsy values include `false`, `0`, empty strings, `null` and empty collections.

```html
<p data-sly-test="${hero.subtitle}">${hero.subtitle}</p>
```

You can store the result in a variable by adding an identifier, which is handy for avoiding repeated work and for writing an "else" branch (HTL has no `else` keyword):

```html
<div data-sly-test.hasItems="${items && items.size > 0}">
    <!-- show items -->
</div>
<p data-sly-test="${!hasItems}">Nothing to display.</p>
```

## data-sly-set

Assigns a value to a variable, which you can then reuse in the template. It was added in HTL 1.4, so make sure your AEM version supports it (AEM as a Cloud Service does).

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

## data-sly-text

Replaces the element's content with the evaluated expression. The placeholder text inside the element is ignored at render time, which is useful for prototyping markup.

```html
<p data-sly-text="${properties.jcr:description}">Lorem ipsum placeholder</p>
```

For simple cases, plain `<p>${properties.jcr:description}</p>` is cleaner and equivalent.

## data-sly-attribute

Sets attributes on the element. Use the dotted form for one attribute:

```html
<a data-sly-attribute.href="${link.url}" data-sly-attribute.title="${link.title}">Read more</a>
```

Or pass a **map** to set several attributes at once:

```html
<div data-sly-attribute="${model.attributes}"></div>
```

Behavior worth remembering:

- A value of `false`, `null` or an empty string **removes** the attribute
- A value of `true` outputs a **boolean attribute** (for example, `checked`)

```html
<input type="checkbox" data-sly-attribute.checked="${model.isSelected}"/>
```

## data-sly-element

Changes the element's tag name dynamically. A classic use is a heading whose level is chosen by the author:

```html
<h2 data-sly-element="${model.headingLevel}">${model.title}</h2>
```

For security, HTL only allows a safe list of element names here, and anything else is ignored.

## data-sly-list

Iterates over a collection, repeating the element's **content** while the host element appears once. Inside the loop you get the item and an `itemList` helper object.

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

Use a custom identifier to rename the variables (the helper is named after it):

```html
<ul data-sly-list.link="${model.links}">
    <li class="${linkList.first ? 'first' : ''}">
        <a href="${link.url}">${link.label}</a>
    </li>
</ul>
```

The `itemList` object exposes:

| Property | Meaning |
| --- | --- |
| `index` | Zero-based position |
| `count` | One-based position |
| `first` / `last` | True for the first / last item |
| `middle` | True for items that are neither first nor last |
| `odd` / `even` | True when the zero-based `index` is odd / even |

You can also limit iteration with `begin`, `end` and `step` options:

```html
<li data-sly-list.item="${model.items @ begin=0, end=2}">${item.title}</li>
```

## data-sly-repeat

Works like `data-sly-list`, except it repeats the **host element itself**. This avoids wrapper elements and extra `<sly>` tags, and it is generally the preferred choice.

```html
<div class="card" data-sly-repeat.card="${model.cards}">
    <h3>${card.title}</h3>
    <p>${card.description}</p>
</div>
```

This outputs one `<div class="card">` per item. With `data-sly-list` you would need an extra wrapper.

## data-sly-resource

Renders **another resource** using that resource's own component and script. Use it to include child components, such as the items inside a container:

```html
<div data-sly-resource="${'content' @ resourceType='myproject/components/text'}"></div>
```

Combined with a loop, it renders each child with its own component:

```html
<div data-sly-repeat.child="${container.children}" data-sly-resource="${child.path}"></div>
```

Useful options include `resourceType`, `selectors`, `addSelectors`, `removeSelectors`, `prependPath`, `appendPath` and `wcmmode`.

## data-sly-include

Includes the output of **another script** in the *current* resource's context. Unlike `data-sly-resource`, it does not switch to a different resource or component.

```html
<sly data-sly-include="header.html"></sly>
```

Rule of thumb: use `data-sly-resource` to render *content*, and `data-sly-include` to reuse *markup fragments* of the same component.

## data-sly-template and data-sly-call

Templates are reusable markup blocks with parameters. Define one with `data-sly-template`:

```html
<template data-sly-template.card="${@ title, text}">
    <div class="card">
        <h3>${title}</h3>
        <p>${text}</p>
    </div>
</template>
```

Call it (in the same file) with `data-sly-call`:

```html
<sly data-sly-call="${card @ title='Hello', text='World'}"></sly>
```

Or load it from another file with `data-sly-use` and call it through the identifier:

```html
<sly data-sly-use.lib="templates/cards.html"></sly>
<sly data-sly-call="${lib.card @ title=model.title, text=model.text}"></sly>
```

A template is never rendered on its own. It only produces output when called.

## data-sly-unwrap

Removes the host element from the output while keeping its content. This is useful when you need an element only to carry other block statements.

```html
<div data-sly-unwrap>
    <span>Only the span is output</span>
</div>
```

It also accepts a condition (HTL 1.4+):

```html
<div data-sly-unwrap="${model.skipWrapper}">...</div>
```

In practice, the `<sly>` element (below) is usually cleaner than unwrapping.

## The `<sly>` Element

`<sly>` is a special element that is **removed from the output** while its block statements still run. Use it when no real element could carry the statement:

```html
<sly data-sly-test="${wcmmode.edit}">
    <p class="cq-placeholder">Configure this component</p>
</sly>
```

Write it as `<sly></sly>` rather than the self-closing `<sly/>`, since `sly` is not a void element.

## Evaluation Order When Statements Share an Element

When several block statements sit on one element, HTL evaluates them in a fixed priority order, not the order you wrote them:

1. `data-sly-template`
2. `data-sly-set`, `data-sly-test`, `data-sly-use`
3. `data-sly-call`
4. `data-sly-text`
5. `data-sly-element`, `data-sly-include`, `data-sly-resource`
6. `data-sly-unwrap`
7. `data-sly-list`, `data-sly-repeat`
8. `data-sly-attribute`

Statements at the same level run left to right. This explains why `data-sly-test` combined with `data-sly-repeat` on the same element works as expected: the test runs first, and the loop only starts if it passes.

```html
<li data-sly-test="${model.items}" data-sly-repeat.item="${model.items}">${item.title}</li>
```

## Expression Options Worth Knowing

Options follow `@` inside an expression and are not block statements, but you'll use them constantly:

| Option | Example | Effect |
| --- | --- | --- |
| `context` | `${text @ context='html'}` | Sets the output escaping context (explained next) |
| `i18n` | `${'Read more' @ i18n}` | Looks up a translation |
| `format` | `${'Page {0} of {1}' @ format=[current, total]}` | String formatting |
| `join` | `${tags @ join=', '}` | Joins a collection into a string |

You can combine several options, separated by commas: `${'Read more' @ i18n, context='text'}`.

## Display Contexts in Depth

HTL's biggest security feature is that it **escapes output automatically**, based on *where* in the markup the expression appears. This protects you from XSS without any extra effort. The rule of thumb: **let HTL pick the context, and override it only when you must.**

### What HTL Applies Automatically

| Where the expression sits | Context applied |
| --- | --- |
| Element content (`<p>${x}</p>`) | `text` |
| Attribute value (`<div title="${x}">`) | `attribute` |
| `href` / `src` and similar URL attributes | `uri` |
| Inside a `<script>` or `<style>` block | **No default**, you must set one |

That last row catches many beginners. In HTL, an expression inside a `<script>` or `<style>` element with **no explicit context outputs nothing at all**. That's deliberate, so that unsafe values never reach script code by accident.

### The Contexts You Can Set

| Context | Use it for | Example |
| --- | --- | --- |
| `text` | Plain text in element content (default) | `${title}` |
| `html` | Rich text that should keep safe markup | `${model.richText @ context='html'}` |
| `attribute` | Attribute values (default in attributes) | `title="${title}"` |
| `uri` | Links and URLs | `href="${link @ context='uri'}"` |
| `number` | Numeric values, defaulting to `0` if invalid | `${count @ context='number'}` |
| `attributeName` | A dynamic attribute *name* | `data-sly-attribute` handles this for you |
| `elementName` | A dynamic element *name* | `data-sly-element` handles this for you |
| `scriptString` | A value inside a JavaScript string literal | `var name = '${user.name @ context='scriptString'}';` |
| `scriptToken` | A JavaScript identifier or keyword | rare |
| `scriptComment` | Inside a JavaScript comment | rare |
| `scriptRegExp` | Inside a JavaScript regular expression | rare |
| `styleString` | A value inside a CSS string | `content: '${text @ context='styleString'}';` |
| `styleToken` | A CSS identifier or value | `color: ${color @ context='styleToken'};` |
| `styleComment` | Inside a CSS comment | rare |
| `unsafe` | **Disables all escaping** | trusted values only |

### Example: Rich Text from a Dialog

The rich text editor in a dialog stores HTML. If you output it with the default `text` context, the tags show up as visible characters (`&lt;p&gt;...`). Use `html`:

```html
<div class="text">
    ${model.text @ context='html'}
</div>
```

With `context='html'`, HTL runs the value through AEM's XSS filter, which keeps safe markup (paragraphs, links, bold) and strips dangerous parts like `<script>` tags or `onclick` handlers.

### Example: Passing Data to JavaScript

```html
<script>
    var componentConfig = {
        title: '${model.title @ context='scriptString'}',
        count: ${model.count @ context='number'}
    };
</script>
```

Without the explicit contexts, both values would render as empty. Alternatively, put the data in a `data-` attribute and read it from your client library, which avoids inline scripts entirely:

```html
<div class="carousel" data-config="${model.configJson}"></div>
```

### Using `unsafe`

`context='unsafe'` turns escaping **off**. Only use it for content you fully control (for example, markup generated by your own trusted code), and never for author-entered or user-supplied values. If you find yourself reaching for it, try `html` first.

### Context Checklist

- Rich text from the RTE → `context='html'`
- A URL built in code → `context='uri'`
- Data inside inline `<script>` → `scriptString` or `number`
- Everything else → leave the default alone

## Operators in HTL Expressions

HTL expressions support a deliberately small set of operators. Business logic belongs in your Sling Model, so HTL has **no arithmetic operators** (no `+`, `-`, `*`, `/`). The ones you do get cover conditions and fallbacks.

### Unary Operator: Logical NOT (`!`)

The only unary operator is `!`, which inverts the truthiness of its operand. It is the standard way to write the "else" branch, since HTL has no `else` keyword:

```html
<div data-sly-test.hasItems="${model.items}">
    <ul data-sly-list="${model.items}">
        <li>${item.title}</li>
    </ul>
</div>
<p data-sly-test="${!hasItems}">Nothing to display.</p>
```

Remember what counts as **falsy**: `false`, `0`, an empty string, `null`, and an empty collection. So `${!model.items}` is true when the list is empty or missing, not just when it's `null`.

You can negate a whole expression with parentheses:

```html
<p data-sly-test="${!(page.hidden || page.expired)}">Visible page</p>
```

To force a value to a boolean, use double negation:

```html
<div data-sly-attribute.hidden="${!!model.isHidden}"></div>
```

### Logical Operators: `&&` and `||`

```html
<!-- Both must be truthy -->
<div data-sly-test="${hero.title && hero.image}">...</div>

<!-- Either one is enough -->
<div data-sly-test="${hero.title || hero.subtitle}">...</div>
```

`||` also works as a **fallback**, because it returns the first truthy value:

```html
<h1>${hero.title || 'Welcome'}</h1>
```

### Ternary Operator: `condition ? a : b`

The conditional operator picks between two values inline, which is ideal for class names and attribute values:

```html
<li class="${itemList.first ? 'first' : (itemList.last ? 'last' : 'middle')}">
```

Keep ternaries simple. Nested ones are hard to read, so move complex decisions into the Sling Model and expose a single `getCssClass()` method.

### Comparison Operators

| Operator | Meaning |
| --- | --- |
| `==` / `!=` | Equal / not equal (strict, no type coercion) |
| `<` `<=` `>` `>=` | Less / greater than (numbers) |

```html
<p data-sly-test="${model.items.size > 3}">Showing the top 3 items only.</p>
<span data-sly-test="${hero.alignment == 'center'}" class="is-centered"></span>
```

Because equality is strict, `'5' == 5` is **false**. Make sure both sides have the same type (a common source of "why is my test failing?" confusion).

### Grouping and Precedence

Operators are evaluated in this order, from highest to lowest priority: `!`, then comparisons, then `&&`, then `||`, then the ternary operator. When in doubt, use parentheses to make your intent explicit:

```html
<div data-sly-test="${(hero.title || hero.subtitle) && !hero.hidden}">...</div>
```

### Operator Quick Reference

| Operator | Type | Typical use |
| --- | --- | --- |
| `!` | Unary | Negate a condition, "else" branches |
| `&&` | Logical | Require several conditions |
| \` |  | \` |
| `? :` | Ternary | Inline choice of value or CSS class |
| `==` `!=` | Comparison | Match strings and numbers |
| `<` `<=` `>` `>=` | Comparison | Numeric thresholds |

## Style Tips from the Core Components Team

- Put block statements **before** regular attributes, so variables are available to the attributes that follow
- Prefer `data-sly-repeat` over `data-sly-list` to avoid unnecessary `<sly>` wrappers
- Prefer `<sly>` over `<div data-sly-unwrap>`
- Place `data-sly-use` on a top-level element, near the start of the template
- Avoid `data-sly-text`, `data-sly-attribute` and `data-sly-element` where plain expressions will do

## What's Next

Our `herobanner.html` still depends on a `HeroBanner` Sling Model that doesn't exist yet. In the next post we'll build it and see how Sling Models connect your Java logic to everything you've just learned in HTL.

---

*Next in this series: \[Sling Models 101\]*
