HTL (Sightly) is the HTML templating system for AEM. Our style guide for frontend and backend developers helps to keep your scripts clean and easy to read.
When Adobe Experience Manager (AEM) 6.0 was introduced, it came with a HTML templating system called HTL, formerly known as Sightly. HTL (Sightly) replaces JSP/ESP files and is now the preferred way of processing HTML files on the server for AEM projects.
HTL (Sightly) is the middle layer where frontend and backend developers come together and work on the same files. It’s important that both sides develop in a common way so that the HTL (Sightly) scripts remain clean and easy to read. The problems that often arise over time:
Often developers write their HTL (Sightly) code with different priorities in mind. For frontend developers, the HTML must be well structured, semantic and accessible. While for backend developers the data, validation, and security are the most important topics. This results in a variety of code styles and ways to write HTL (Sightly) scripts.
I created this style guide to maintain a consistent code base and avoid confusion between developers. These easy to follow rules will help to make HTL (Sightly) live up to the meaning of its name.
The name “Sightly” (meaning “pleasing to the eye”) highlights its focus on keeping your markup beautiful, and thus maintainable, once made dynamic.
In order to encourage keeping a clean separation of concerns, HTL has by design some limitations for inline JavaScript or CSS. First, because HTL doesn't parse JavaScript or CSS, and therefore cannot automatically define the corresponding escaping, all expressions written there must provide an explicit context option. Then, because the HTML grammar ignores elements located inside a <script> or <style> elements, no block statement can be used within them.
Therefore, JavaScript and CSS code should instead be placed into corresponding .js and .css files. Data attributes are the easiest way to communicate values to JavaScript, and class names are the best way to trigger specific styles.
<!--/* Bad */--> <section class="teaser" data-sly-use.teaser="com.example.TeaserComponent"> <h2 class="teaser__title">${teaser.title}</h2> <script> var teaserConfig = { skin: "${teaser.skin @ context='scriptString'}", animationSpeed: ${teaser.animationSpeed @ context='number'} }; </script> <style> .teaser__title { font-size: ${teaser.titleFontSize @ context='styleToken'} } </style> </section> <!--/* Good */--> <section class="teaser" data-sly-use.teaser="com.example.TeaserComponent" data-teaser-config="${teaser.jsonConfig}"> <h2 class="teaser__title teaser__title--font-${teaser.titleFontClass}">${teaser.title}</h2> </section>
Normal HTML comments get rendered to the final markup. To keep the DOM clean, always use HTL comments over normal HTML comments.
<!-- Never use HTML comments --> <!--/* Always use HTL comments */-->
In most cases, you can leave out the display context, because it is determined automatically.
<!--/* Bad */--> <a href="${teaser.link @ context = 'uri'}"></a> <!--/* Good */--> <a href="${teaser.link}"></a>
From the following list of contexts, always choose the one closest to the top that fits your needs:
number: For whole numbers (in HTML, JS or CSS)
uri: For links and paths (in HTML, JS or CSS, applied by default for src and href attributes)
elementName: For HTML element names (applied by default by data-sly-element)
attributeName: For HTML attribute names (applied by default by data-sly-attribute for attribute names)
scriptToken: For JavaScript identifiers and keywords
styleToken: For CSS identifiers and keywords
scriptString: For text within JavaScript strings
styleString: For text within CSS strings
attribute: For HTML attribute values (applied by default for attribute values)
text: For HTML text content (applied by default for any content)
html: For HTML markup (it filters out all elements and attributes that could be dangerous)
unsafe: Unescaped and unfiltered direct output
<!--/* Bad */--> <section class="teaser" data-sly-use.teaser="com.example.TeaserComponent"> <h4 onclick="${teaser.clickHandler @ context='unsafe'}">${teaser.title}</h4> <div style="color: ${teaser.color @ context='unsafe'};"> ${teaser.htmlContent @ context='unsafe'} </div> </section> <!--/* Good */--> <section class="teaser" data-sly-use.teaser="com.example.TeaserComponent"> <h4 onclick="${teaser.clickHandler @ context='scriptToken'}">${teaser.title}</h4> <div style="color: ${teaser.color @ context='styleToken'};"> ${teaser.htmlContent @ context='html'} </div> </section>
It might sound obvious, but an expression with just a string literal inside equals just that string literal.
<!--/* Bad */--> <sly data-sly-use.clientlib="${'/libs/granite/sightly/templates/clientlib.html'}"> ... </sly> <!--/* Good */--> <sly data-sly-use.clientlib="/libs/granite/sightly/templates/clientlib.html"> ... </sly>
HTML elements with the tag name SLY are automatically getting unwrapped and will not be part of the final markup.
<!--/* Bad */--> <div data-sly-include="content.html" data-sly-unwrap></div> <!--/* Bad */--> <div data-sly-resource="${item @ selectors='event'}" data-sly-unwrap></div> <!--/* Bad */--> <div data-sly-test="${event.hasDate}" data-sly-unwrap> ... </div> <!--/* Good */--> <sly data-sly-include="content.html"></sly> <!--/* Good */--> <sly data-sly-resource="${item @ selectors = 'event'}"></sly> <!--/* Good */--> <sly data-sly-test="${event.hasDate}"> ... </sly>
IMPORTANT - The SLY element will not automatically unwrap itself if you use HTL 1.0 (AEM 6.0). In that case, you still have to add the "data-sly-unwrap" attribute.
<!--/* Bad - HTL 1.0 */--> <sly data-sly-include="content.html"></sly> <!--/* Good - HTL 1.0 */--> <sly data-sly-include="content.html" data-sly-unwrap></sly>
Since data-sly-use identifiers are always global (https://docs.adobe.com/docs/en/htl/docs/use-api/java.html#Local%20identifier), these attributes should only be placed in the top-level element. That way one can easily see name clashes and also it prevents initializing the same object twice.
<!--/* Bad */--> <section class="teaser"> <h3 data-sly-use.teaser="com.example.TeaserComponent">${teaser.title}</h3> </section> <!--/* Good */--> <section class="teaser" data-sly-use.teaser="com.example.TeaserComponent"> <h3>${teaser.title}</h3> </section>
This will enhance the readability of your HTL scripts and makes it easier for others to understand.
<!--/* Bad */--> <sly data-sly-use.comp="com.example.TeaserComponent"> ... </sly> <!--/* Good */--> <sly data-sly-use.teaser="com.example.TeaserComponent"> ... </sly>
Using camelCase will help to increase the readability of your identifiers. Notice though that HTL will internally only use (and log) lowercase identifiers. Also, dashes are not allowed for identifiers.
<!--/* Bad */--> <sly data-sly-use.mediagallery="com.example.MediaGallery"> ... </sly> <!--/* Good */--> <sly data-sly-use.mediaGallery="com.example.MediaGallery"> ... </sly>
<!--/* Bad */--> <section class="teaser" data-sly-test="${!teaser.empty}"> ... </section> <div class="cq-placeholder" data-sly-test="${teaser.empty}"></div> <!--/* Good */--> <section class="teaser" data-sly-test.hasContent="${!teaser.empty}"> ... </section> <div class="cq-placeholder" data-sly-test="${!hasContent}"></div>
<!--/* Bad */--> <ul class="tagList" data-sly-list="${tagList.tags}"> <li class="tagList__tag"> <a class="tagList__button" href="${item.url}">${item.title}</a> </li> </ul> <!--/* Good */--> <ul class="tagList" data-sly-list.tag="${tagList.tags}"> <li class="tagList__tag"> <a class="tagList__button" href="${tag.url}">${tag.title}</a> </li> </ul>
<!--/* Bad */--> <p data-sly-test="${teaser.text}" class="teaser__text"></p> <!--/* Good */--> <p class="teaser__text" data-sly-test="${teaser.text}"></p>
<!--/* Bad */--> <sly data-sly-test="${!teaser.active}"> <section class="teaser"> … </section> </sly> <!--/* Good */--> <section class="teaser" data-sly-test="${!teaser.active}"> … </section>
<!--/* Bad */--> <div data-sly-element="${headlineElement}">${event.year}</div> <a href="#" data-sly-attribute.href="${event.link}"></a> <p class="event__year" data-sly-text="${event.year}"></p> <!--/* Good */--> <h2>${event.year}</h2> <a href="${event.link}"></a> <p class="event__year">${event.year}</p>
<!--/* Bad */--> <sly data-sly-use.teaser="com.example.TeaserComponent"> <sly data-sly-template.teaserSmall="${@ title, text}"> <h2>${title}</h2> <p>${text}</p> </sly> <sly data-sly-call="${teaserSmall @ title=teaser.title, text=teaser.text}"></sly> </sly> <!--/* Good - Separate template file: "teaser-templates.html" */--> <sly data-sly-template.teaserSmall="${@ teaserModel}"> <h2>${teaserModel.title}</h2> <p>${teaserModel.text}</p> </sly> <!--/* Good - HTL script */--> <sly data-sly-use.teaser="com.example.TeaserComponent" data-sly-use.teaserTemplates="teaser-templates.html"> <sly data-sly-call="${teaserTemplates.teaserSmall @ teaserModel=teaser}"></sly> </sly>
This style guide is also published as an open source project on Github. Do you have new ideas or feedback? Please create a pull request or open an issue if you want to contribute.