Rendering AEM Components with HTL Lists and Repeats
Adobe Experience Manager components often need to render collections: cards, navigation links, related articles, product variations, or author-selected resources. HTL, previously known as Sightly, provides two useful iteration statements for this work: data-sly-list and data-sly-repeat. Both keep presentation logic in the template while allowing Java models to prepare structured data.
The distinction matters when building maintainable AEM sites for Australian organisations. A component designed for a Sydney retailer, a Melbourne university, or a government service in Canberra may need predictable HTML, accessible markup, and efficient rendering across authoring and publishing environments. Choosing the right iteration statement helps keep those requirements visible in the component code.
How HTL Iteration Works
data-sly-list is generally used when a parent element contains repeated child content. It is a natural choice for lists, grids, and collections where the wrapper should remain singular. A basic product example looks like this:
<ul data-sly-list.product="${model.products}">
<li>
<a href="${product.link}">${product.title}</a>
</li>
</ul>
The ul remains the container, while the nested li is rendered for every product. Naming the iteration variable explicitly, as in product, is clearer than relying on HTL’s default item variable, especially when a component contains several collections.
data-sly-repeat repeats the element carrying the statement. This makes it useful when each item needs to generate a complete, independent element, such as a card or an article teaser:
<article data-sly-repeat.card="${model.cards}" class="card">
<h3>${card.title}</h3>
<p>${card.summary}</p>
</article>
With repeat, the article itself is created once per card. This avoids adding an unnecessary wrapper and can be convenient when the surrounding structure is controlled by a parent component. Teams documenting conference material or reviewing examples can also use the conference app to keep session resources available while testing these patterns.
Choosing Between List And Repeat
The simplest decision is structural. Use data-sly-list when the component has one parent container and repeated content inside it. Use data-sly-repeat when the element carrying the statement should be duplicated. This difference becomes important for CSS selectors, accessibility landmarks, and responsive layouts.
A list can expose metadata through the list variable. With a named collection, the accompanying metadata object follows the variable name:
<ul data-sly-list.item="${model.items}">
<li class="${itemList.first ? 'is-first' : ''}">
${itemList.index}: ${item.name}
</li>
</ul>
Properties such as index, count, first, middle, and last support presentation decisions without JavaScript. For example, a final item might receive a special class, or separators could be omitted after the last navigation link. Keep such conditions modest; complex business rules belong in the Sling Model rather than in HTL.
data-sly-repeat is often more direct for a component whose outer element is meaningful. A series of <li> elements, <article> cards, or <option> values can be generated without introducing another wrapper. If a design system expects a specific DOM hierarchy, inspect the rendered markup rather than selecting a statement based only on visual output.
A Practical Comparison
The two statements can render similar results, but their responsibilities differ. The following comparison helps when reviewing an AEM component during development or code review.
| Concern | data-sly-list |
data-sly-repeat |
|---|---|---|
| Main purpose | Repeats content within a parent element | Repeats the element carrying the statement |
| Typical use | <ul> with repeated <li> content |
Repeated cards, articles, or list items |
| Wrapper behaviour | Keeps one parent wrapper | Creates one copy of the host element per item |
| Metadata | Provides useful list metadata such as index and last | Best suited to straightforward item rendering |
| Markup control | Good for stable collection structure | Good for avoiding unnecessary wrappers |
| Readability | Clear when the collection has a shared container | Clear when each item is a complete component unit |
For example, a navigation component may use data-sly-list because all links belong inside one <nav> and <ul>. A search result component may use data-sly-repeat on each <article>, especially when the parent template already supplies the results region.
Both statements should receive a well-defined collection. A model can return an empty list rather than null, reducing defensive template code. If the empty state is important, render it separately:
<div data-sly-test="${!model.items || model.items.size == 0}">
No results are available.
</div>
In production code, prefer a model property such as model.hasItems when the condition needs more than a simple collection check. This keeps HTL focused on output and makes unit testing easier.
Component Design And Performance
A clean HTL template begins with a model that exposes presentation-ready values. The Sling Model should handle repository access, sorting, filtering, link mapping, date formatting, and permissions. HTL should decide where those values appear, not reproduce repository queries or complicated transformations for every item.
This separation is especially valuable in Australian projects where content may be served across large geographic distances, from Perth to Brisbane, and where caching and publish performance matter. Iteration itself is usually inexpensive, but a model that performs repository work inside a loop can create avoidable delays. Resolve related resources efficiently before the template starts rendering.
Stable authoring behaviour matters as well. Multifield data should be normalised into predictable objects, with safe defaults for missing titles, images, and URLs. Use the AEM Core Components as a reference for responsive images, link handling, and accessibility attributes, rather than treating a short HTL file as a complete implementation.
Workflow and publishing dependencies should receive the same attention. A component that displays approval-sensitive content may need a reliable escalation path, so the workflow escalation guide can be useful when connecting content operations with rendering requirements. Rendering logic should remain independent of workflow state wherever possible, while models can expose only content that is approved for the current context.
Practical Checks Before Release
Iteration templates often look correct in a browser while producing problems for screen readers, crawlers, or authors. Test the rendered HTML at representative collection sizes, including zero items, one item, and a long list. Validate links, image alternatives, heading order, and keyboard behaviour rather than checking only the visual layout.
Useful review points include:
- Confirm that the chosen statement produces the intended wrapper structure.
- Use named variables instead of ambiguous default item names.
- Test empty, missing, and partially authored collection values.
- Check
first,last, and index logic at the collection boundaries.
Performance testing should include publish mode and dispatcher caching. A component that works with six cards in local authoring may behave differently when a listing contains hundreds of results. Pagination, sensible query limits, and cacheable model output are preferable to rendering an unbounded collection.
For Australian websites, include local delivery and compliance checks before release:
- Verify date, currency, phone, and address formats used in the component.
- Test responsive layouts on common mobile devices used across Sydney and Melbourne.
- Review WCAG accessibility expectations for public-sector and enterprise sites.
- Confirm privacy-sensitive content is excluded from cached or shared responses.
The local market also makes content governance important. Retail, education, finance, and government teams often share components across brands or regional sites, so a model should avoid hard-coded assumptions about Australian states, tax labels, or service areas. Pass those values through configuration or authored content when the component needs them.
A well-structured component may use both statements in the same template. For example, data-sly-list can preserve a single grid wrapper, while a nested data-sly-repeat can render each item’s related tag. Keep nesting shallow and name each collection clearly. Deeply nested iteration quickly becomes difficult to debug and may indicate that the model needs a stronger view model.
When debugging, inspect the generated HTML and the model’s actual values. HTL silently omits some invalid or empty output, which can make a data problem appear to be a markup problem. Logging belongs in the model or service layer, while temporary author-facing diagnostics should never be left in a production component.
Use these patterns to build a small reusable component example, test it with realistic content, and compare the output against the project’s design and accessibility standards. Then review the template with both an AEM developer and a front-end engineer before promoting it through the deployment pipeline.