HTL deep dive
Purpose: Full working reference for HTL: blocks, expression options, contexts, and template/call reuse.
Who this page is for
| Audience | Why it matters to you |
|---|---|
| Frontend engineers | Daily reference |
Block statements
| Statement | Purpose | Note |
|---|---|---|
| data-sly-use | Bind a model/template/JS use-object | data-sly-use.m="…PlanCardModel" |
| data-sly-test | Conditional render | Store result: data-sly-test.hasPlans="…" reuses it |
| data-sly-list / data-sly-repeat | Iterate (list wraps, repeat repeats the element) | itemList.index/first/last |
| data-sly-resource | Include another resource/component | The composition primitive |
| data-sly-include | Include another script (same context) | Script split, not component include |
| data-sly-template / data-sly-call | Define/call a markup function | Parameterised partials |
| data-sly-attribute | Set attributes from a map | Drops empty attributes safely |
| data-sly-element | Change the element tag name | — |
| data-sly-unwrap | Render children without the wrapper element | Prefer <sly> element |
Expression options
${model.title @ context='text'} escaping context (see below)
${model.ctaPath @ extension='html'} proper link building
${'page.label' @ i18n} translation
${model.items @ join=', '} list join
${properties.date @ format='dd MMM yyyy', locale=request.locale}
Contexts (XSS protection)
HTL escapes per context — the reason "just render it" is safe by default:
| Context | Use for |
|---|---|
| text (default in body) | Visible text |
| attribute (default in attrs) | Attribute values |
| uri | href/src — rejects javascript: etc. |
| html | Rich text from RTE — sanitises markup |
| scriptToken/styleToken etc. | Rare, deliberate cases |
| unsafe | Disables protection — requires security review, almost never justified |
Rich text pattern: ${model.disclaimerHtml @ context='html'} — sanitised, tags preserved.
Template reuse
<template data-sly-template.benefitRow="${@ benefit}">
<li class="${benefit.covered ? 'tick' : 'cross'}">${benefit.label}</li>
</template>
<ul data-sly-list.b="${model.benefits}">
<sly data-sly-call="${benefitRow @ benefit=b}"></sly>
</ul>
Shared template libraries: put templates in a common file and data-sly-use it from any component.