Coding guidelines, best practices, and the style guide to help ensure consistency and accuracy across the entire project.
Use soft tabs (4 spaces) for indentation.
End all files with a newline as the last character. (Exception: JSP/JSPF files often end without a trailing newline on purpose. Text outside scriptlet tags is emitted verbatim, so a trailing newline can become stray whitespace in the response, which matters for POST handlers, JSON/XML emitters, and head partials.)
Variable, function, and CSS selector names should be full words and descriptive. Avoid single-letter names or unclear abbreviations.
Use camelCase for all naming conventions in Java, JSP, JavaScript, and node property names in AEM.
Use kebab-case to name selector classes in CSS.
In most cases, use kebab-case for folder and file names.
Use US English for all naming conventions, including variables, functions, classes, and files. However, use Canadian English for comments and user-facing content.
// Force black colour on links for dark backgrounds
switch (bgColor) {
case "C8C8C8":
btnColor = "btn-simple-black";
break;
...
}
Use Sentence case for headings and titles, except for proper nouns. Also capitalise the first word after a colon or dash, treating each segment as its own sentence (e.g. “Step 2: Verify the build”). (Examples: “Card component”, “Anchor Navigation component”, “Icon colour”)
Use braces for all control structures (i.e., if, else, for, do, while, etc.), even if the body contains only a single statement.
Place the else or else if after the previous closing brace } without a line break.
Always put a space before the opening brace {.
Always put the closing brace } on a new line.
Bad
if (someCondition()) doSomething();
if (someCondition()) { doSomething() };
if (someCondition())
doSomething();
if (someCondition()) {
doSomething();
}
else {
doSomethingElse();
}
for (let i = 0; i < foo.length; i++) bar(foo[i]);
Good
if (someCondition()) {
doSomething();
}
if (someCondition()) {
doSomething();
} else {
doSomethingElse();
}
for (let i = 0; i < foo.length; i++) {
bar(foo[i]);
}
if (!linkURL.contains(".html")
&& linkURL.startsWith("/content/")
&& !linkURL.startsWith("/content/dam")
) {
linkURL += ".html";
}
card-deck__intro-text--large-highlightid attribute for ARIA, form labels, data-bs-target, or JavaScript. Just don’t write #foo in a rule; use a class instead.&:hover,
&:active {
text-decoration: none;
}
padding: 1.5rem 0.5rem;
Put a space before the opening brace { in rule declarations.
In properties, put a space after, but not before, the : character.
Put closing braces } of rule declarations on a new line.
Put blank lines between rule declarations.
font, margin, padding, background).Bad
.card{
margin:1rem; padding:1rem;
border:1px solid black; }
.card-title{ font-size:1.5rem; }
Good
.card {
margin: 1rem;
padding: 1rem;
border: 1px solid #000;
}
.card-title {
font-size: 1.5rem;
}
A CSS ruleset is a set of one or more CSS declarations applied to a specific HTML element or group of elements. A CSS ruleset consists of two primary components: the selector and the declaration block.
Selector
p {
color: blue;
}
Class selector
.card {
color: blue;
}
Declaration block
p {
color: blue;
}
Declaration
p {
color: blue;
}
Property
p {
color: blue;
}
Value
p {
color: blue;
}
A consistent ordering of CSS rules facilitates a quicker scan of a declaration block.
contentposition z-index top bottom left right float cleardisplay visibility opacity flex-direction flex-wrap flex-flow justify-content align-items align-content order flex-grow flex-shrink flex-basis gap flex transformoverflow clipanimation transitionmargin margin-top margin-right margin-bottom margin-left box-shadow border border-radius box-sizing width min-width max-width height min-height max-height paddingbackground cursorfont-size line-height font-family font-weight font-style text-align text-transform word-spacing letter-spacing color:hover :focus :active ::before ::after :first-child :last-childFollow the Bootstrap 5 default breakpoints whenever possible: https://getbootstrap.com/docs/5.3/layout/breakpoints/
Our most common breakpoints are:
| Breakpoint | Use cases | Class infix | Dimensions |
|---|---|---|---|
| Extra small | Portrait phones, less than 576px | None | <576px |
| Small | Small devices (landscape phones, 576px and up) | sm | >=576px |
| Medium | Medium devices (tablets, 768px and up) | md | >=768px |
| Large | Large devices (desktops, 992px and up) | lg | >=992px |
| Extra large | X-Large devices (large desktops, 1200px and up) | xl | >=1200px |
It’s recommended to use a mobile-first approach by initially declaring styles that will appear on smaller devices and then adjusting styles for larger devices using media queries.
Mobile-first approach
li {
// Style on tablet/mobile
font-size: 14px;
// Style on desktop (>=992px)
@media (min-width: 992px) {
font-size: 18px;
}
}
Sometimes it’s necessary to define styles in a granular way for more breakpoints
.li {
// Style on extra small devices (<576px)
font-size: 14px;
// Style on small devices (>=576px and <768px)
@media (min-width: 576px) and (max-width: 767px) {
font-size: 16px;
}
// Style on medium devices (>=768px and <992px)
@media (min-width: 768px) and (max-width: 991px) {
font-size: 18px;
}
// Style on large devices (>=992px and <1200px)
@media (min-width: 992px) and (max-width: 1199px) {
font-size: 20px;
}
// Style on extra large devices (>=1200px)
@media (min-width: 1200px) {
font-size: 22px;
}
}
However, there are cases where it’s easier to use a desktop-first approach by initially declaring styles that will appear on larger devices and then adjusting styles for smaller devices using media queries. We sometimes use this approach in legacy code for backwards compatibility.
Desktop-first approach
li {
// Style on desktop
font-size: 18px;
// Style on tablet/mobile (<992px)
@media (max-width: 991px) {
font-size: 14px;
}
}
Using the Bootstrap 12-column grid system
Here is an example of a column layout w ere the 3 divs will occupy 100% of the width (12 columns) on mobile (<576px), 50% (6 columns) on tablet (>=576px and <992px), and 33.33% (4 columns) on desktop (>=992px).
<div class="container-fluid">
<div class="row">
<div class="col-12 col-sm-6 col-lg-4">
...
</div>
<div class="col-12 col-sm-6 col-lg-4">
...
</div>
<div class="col-12 col-sm-6 col-lg-4">
...
</div>
</div>
</div>
Use a c- prefix to define the namespace of Concordia’s custom components. This will help immediately identify our components as well as prevent conflicts with external selectors. (Example: .c-card vs. Bootstrap’s .card component that we don’t use.) Refer to the _cq_htmlTag section to learn how to set this up on a component in AEM.
A normal import will add the entire content of the included file into the output file. However, a (reference) import allows the code in that file to be used as needed, either by calling a mixin or extending a selector within it.
@import (reference) url("/etc/designs/concordia/clientlibs/dependencies/less/dependencies.less");
Nested selectors should go last. Add whitespace between your rule declarations and nested selectors, as well as between adjacent nested selectors.
.card {
margin: 1rem;
padding: 1rem;
border: 1px solid black;
.card-title {
font-size: 1.5rem;
}
.card-subtitle {
margin-right: 1.2rem;
}
}
Do not nest selectors more than five levels deep
When selectors become this long, it’s likely the CSS becomes too coupled to the HTML, overly specific, and not reusable.
#boot {
.page-container {
.content {
.profile {
.title {
.large {
// Better stop and rethink your approach
}
}
}
}
}
}
Single-line comments // in LESS are ‘silent’, they don’t show up in the compiled CSS output. Whereas multi-line comments /* ... */ get added to the compiled version.
Because LESS will try to calculate math operators, it’s necessary to escape it in order to pass the string to CSS and allow the browser to make the calculation.
Bad (LESS will calculate this to 2.875rem)
font-size: calc(1.375rem + 1.5vw);
Good
font-size: ~'calc(1.375rem + 1.5vw)';
The same applies to any calc() or rgba() expression that references a CSS custom property: wrap it in ~'...' so LESS passes it through unchanged. (A literal-only calc() with no var() does not need escaping.)
min-height: ~'calc(100vh - var(--cds-header-height))';
background: ~'rgba(var(--cds-rgb-burgundy), 0.5)';
Replace all spaces with %20 in the url value for the SVG string. This will prevent problems during minification.
background: url("data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%3e%3cpath%20stroke='%23912338'%20stroke-width='8%'%20fill-rule='evenodd'%20d='M1.646%204.646a.5.5%200%200%201%20.708%200L8%2010.293l5.646-5.647a.5.5%200%200%201%20.708.708l-6%206a.5.5%200%200%201-.708%200l-6-6a.5.5%200%200%201%200-.708z'/%3e%3c/svg%3e");
It’s encouraged to use BEM for the following reasons:
The BEM naming convention is composed of three parts:
If all three parts are used in a name, it would look like this:
block-name__element-name--modifier-name
BEM syntax with LESS
.block {
// Styles for .block
&__element {
// Styles for .block__element
&--modifier {
color: red;
// Styles for .block__element--modifier
}
}
&--modifier {
// Styles for .block--modifier
// Note that it's OK to add a modifier directly to the block
}
}
If your component has child elements several levels deep, don’t try to represent each level in the class name. A BEM class name for a child element should only include the block name and a single element name.
Don’t do this:
.tab-nav__section__container--primary {
color: red;
}
It should be something like this:
.tab-nav__section--primary-container {
color: red;
}
<div class="d-none d-md-block">Display only on desktop/tablet (>=768px) and hide on mobile (<768px)</div>
<div class="d-block d-md-none">Display only on mobile (<768px) and hide on desktop/tablet (>=768px)</div>
<div class="d-print-none">Display only on screen and hide in print</div>
<div class="d-none d-print-block">Display only on print and hide on screen</div>
<div class="visually-hidden">Meant for screen readers only</div>
<ul class="list-unstyled">
<li>This is a list</li>
<li>It appears completely unstyled</li>
</ul>
Always include the font-family fallback, (in most cases it will be sans-serif). This will help prevent issues with broken characters.
font: normal 48px/56px 'gill-sans-nova-condensed', sans-serif;
...
font: 16px/24px Arial, sans-serif;
Use 0 instead of none to specify that the element has no border. The difference between the two is that some memory is occupied when using the border: none property, whereas border: 0 does not occupy any memory. This is because border: none sets the border-style to none and keeps the border-width to medium. However, border: 0 sets the border-width to 0 as well.
.foo {
border: 0;
}
It is recommended to define line height without a unit next to the number value (referred to as a “unitless” line-height). A number value can be a decimal-based number.
.foo {
line-height: 1.5;
}
Additionally, make sure to explicitly define a line-height value, especially when using custom fonts like Gill Sans Nova. Failure to set this may result in a computed value that is a fraction or decimal, potentially leading to layout shifts caused by the Flash of Unstyled Text.
font: normal 23px/1.5 'gill-sans-nova-condensed', sans-serif;
Top and bottom margins (also called vertical margins) collapse, while top and bottom padding does not. Horizontal margins (left and right), like padding, are always displayed and added together. However, vertical margins do not add. Instead, the larger of the two vertical margins sets the distance between adjacent elements.
.img-one {
margin-bottom: 30px;
margin-right: 20px;
}
.img-two {
margin-top: 20px;
margin-left: 20px;
}
In this example, the horizontal space between the .img-one and .img-two borders is 40 pixels and the vertical space is 30 pixels.
The box-sizing property defines if the padding and borders are included in the final width and height of an element. The default setting is content-box, which includes the padding and borders in the size of the element. This can be changed to border-box to exclude the padding and borders. Note that Concordia’s AEM global styles set border-box for all elements, so you never have to add this declaration to your selectors.
.btn {
box-sizing: border-box;
}
Use a double colon in front of pseudo-elements and a single colon in front of pseudo-classes per the CSS3 standards. Refer to the full list of pseudo types.
blockquote::after {
content: "\201D";
color: #6e6e6e;
}
a:hover {
color: #6e6e6e;
text-decoration: none;
}
Concordia’s global stylesheet automatically appends appropriate icons to links referencing documents (PDF, Doc, XLS, PPT, and Zip). You can also display the icons in the following way:
<i class="icon-pdf"></i>
<i class="icon-doc"></i>
<i class="icon-xls"></i>
<i class="icon-ppt"></i>
<i class="icon-zip"></i>
Always use HTTPS (https:) for all embedded resources, such as images, style sheets, scripts, etc. If the respective file is not available over HTTPS, do not embed it and flag this as a problem to a system administrator or content owner.
Bad
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.4.0/jquery.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/3.4.0/jquery.min.js"></script>
@import "//fonts.googleapis.com/css?family=Open+Sans";
@import "http://fonts.googleapis.com/css?family=Open+Sans";
Good
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.0/jquery.min.js"></script>
@import "https://fonts.googleapis.com/css?family=Open+Sans";
Although there is no column limit recommendation for HTML, you may consider wrapping long lines to improve readability.
<a
class="header-btn menu concordia-icon concordia-hamburger-icon d-block d-lg-none"
data-bs-toggle="collapse"
data-bs-target=".nav-collapse"
aria-expanded="false"
aria-controls="nav-collapse"
role="button"
>
<span class="btn-hidden-label">Menu</span>
</a>
Use double quotation marks when quoting attribute values.
Bad
<a class='maia-button maia-button-secondary'>Sign in</a>
Good
<a class="maia-button maia-button-secondary">Sign in</a>
Add the aria-hidden attribute to elements that should be ignored by screen readers.
<span class="separator" aria-hidden="true">|</span>
Include an aria-hidden="true" and visually-hidden class (if using BS5) to two separate spans.
<p>Here is the name of the initiative <span aria-hidden="true">PLAN/NETØ</span><span class="visually-hidden">Plan net zero</span></p>
Include an empty alt value to images that are decorative or have a decorative purpose.
<img src="image.png" alt="">
Hyphens are not allowed in JavaScript names because they can be mistaken for subtraction attempts.
Use const and let instead of var for variable declarations to write more predictable and maintainable JavaScript code to ensure block scope and help catch hoisting issues.
If your project uses jQuery, use double quotes to match jQuery’s convention.
Avoid ECMAScript 2020+ syntax. Keep JavaScript traditional and easy for a human to read.
Use strict equality === / !==. The one exception is == null, which usefully matches both null and undefined.
For keyboard handlers, use keydown with e.key, never keypress or e.keyCode.
New component JavaScript should be vanilla (no new jQuery, and no SPA frameworks like React or Vue), written as a single namespaced object: const cComponentName = { init: function () { ... } }.
Bad
function setSidebar() {
$(".sidebar").hide();
$(".sidebar").css("background-color", "pink");
}
Good
function setSidebar() {
const sidebarEl = $(".sidebar");
sidebarEl.hide();
sidebarEl.css("background-color", "pink");
}
While jQuery was instrumental in the past, its functionalities are now largely covered by native JavaScript and modern frameworks, making it less essential for new projects. If you’re looking to transition from jQuery to vanilla JavaScript, here’s a helpful website.
Avoid using property value names that are tightly coupled to CSS class selectors in AEM dialogs. Since CSS class names can change, it’s better to use a generic name that describes the field’s purpose. Data outlives code.
Bad
<textStyle
jcr:primaryType="cq:Widget"
defaultValue="c-link-list--style-default"
fieldLabel="Text style"
name="./textStyle"
type="select"
xtype="selection">
<options jcr:primaryType="cq:WidgetCollection">
<default
jcr:primaryType="nt:unstructured"
text="Default"
value="c-link-list--style-default"/>
<boldCondensed
jcr:primaryType="nt:unstructured"
text="Gill Sans Bold Condensed"
value="c-link-list--style-bold"/>
</options>
</textStyle>
Good
<textStyle
jcr:primaryType="cq:Widget"
defaultValue="default"
fieldLabel="Text style"
name="./textStyle"
type="select"
xtype="selection">
<options jcr:primaryType="cq:WidgetCollection">
<default
jcr:primaryType="nt:unstructured"
text="Default"
value="default"/>
<boldCondensed
jcr:primaryType="nt:unstructured"
text="Gill Sans Bold Condensed"
value="bold"/>
</options>
</textStyle>
The _cq_htmlTag specifies the tag and attributes that should wrap a component’s content when it’s rendered. This is allows to define the namespace of Concordia’s custom components. For example, the default wrapper for the anchor-navigation component is:
<div class="anchor-navigation">
However, you would add the following node configuration to customize it to:
<div class="c-anchor-navigation">
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
jcr:primaryType="nt:unstructured"
class="c-anchor-navigation"/>
Refer to the Namespacing section to learn how this relates to CSS.
In the dialog, add the cq:isContainer="{Boolean}false" property to explicitly declare that this component cannot contain other components as children.
Build new components, and JSP → HTL conversions, with HTL and a Sling Model; do not add new JSP. Ship both dialog UIs: a Classic UI dialog.xml and a Touch UI _cq_dialog/, because Classic UI authors are still supported. When converting an existing component, never delete its dialog.xml, even when it has no fields (a fieldless dialog is still valid); mirror it as an empty Touch UI dialog.
For new code, use org.apache.sling.xss.XSSAPI (the Sling API), not the deprecated com.adobe.granite.xss.XSSAPI (the Adobe wrapper).
All JPS files should start with the <%@page session="false"%> line at the top of the page. The second line is to include the /apps/concordia/global.jsp file. Because global.jsp already has several imports, it’s not needed to re-import those packages/classes again.
<%@page session="false"%><%
%><%@include file="/apps/concordia/global.jsp"%><%
%><%@page import="com.day.cq.wcm.foundation.Image,
java.util.Iterator,
com.day.cq.wcm.api.PageFilter,
org.apache.commons.lang.StringUtils,
org.apache.commons.lang3.StringEscapeUtils"%><%
// Your code...
In most cases, place the opening scriptlet tag at the end of the previous line and the closing scriptlet tag at the end of the last Java source code line.
String name = properties.get("name", "");
if (name.startsWith("#") && name.length() > 1) {
name = name.substring(1, name.length());
} %>
<div class="accordion">
<div class="accordion-item"><%
if (!name.isEmpty()) { %>
<a name="<%= name %>" class="accordion-hash"></a><%
} %>
<h3 class="accordion-header">
...
Always put a space between the expression scriptlet tags and the variable.
<a class="btn <%= buttonBlock + " " + buttonSize %>" href="<%= link %>" target="<%= target %>">
<%= label %>
</a>
// The following returns a new string with only the content that was inside of the HTML tags.
yourString.replaceAll("<[^>]*>", "");
//Convert special characters in a string to their corresponding HTML entities
//Requires `import="org.apache.commons.lang3.StringEscapeUtils"`
StringEscapeUtils.escapeHtml4(yourString);
.equals() comparisonsWhen comparing a variable to a string literal (or any constant), place the constant on the left side of .equals(). This prevents a NullPointerException if the variable is null, because a string literal can never be null.
Bad — throws NullPointerException if pageType is null
if (pageType.equals("notice")) {
// ...
}
Good — safely returns false if pageType is null
if ("notice".equals(pageType)) {
// ...
}
The same applies to any constant or enum value. Always call .equals() on the value you know is non-null.
// Bad
if (runMode.equals(Externalizer.PUBLISH)) { ... }
// Good
if (Externalizer.PUBLISH.equals(runMode)) { ... }
A Sling Model is a Java class that maps AEM component dialog values and page data to a POJO (Plain Old Java Object), a simple class with private fields and public getters. Without Sling Models, you would need to manually retrieve properties from the JCR using verbose code. Sling annotations handle this data injection automatically, and HTL templates can then access the values through simple getter methods.
The model is typically structured as follows:
package org.concordia.wcms.core.models.content;
// Sling API imports
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.ScriptVariable;
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;
// AEM imports
import com.day.cq.wcm.api.Page;
// Java imports
import javax.annotation.PostConstruct;
import java.text.SimpleDateFormat;
import java.util.Date;
// Custom imports
import org.concordia.wcms.core.util.DateFunctions;
The @Model annotation registers the class as a Sling Model and defines how it can be adapted.
@Model(
adaptables = {SlingHttpServletRequest.class, Resource.class},
adapters = LastUpdatedModel.class,
resourceType = "concordia/components/last-updated",
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
public class LastUpdatedModel {
// ...
}
Attributes:
SlingHttpServletRequest.class when you need page-level data (like currentPage). Use Resource.class when you only need the component’s dialog values. Including both gives you access to everything.LastUpdatedModel.class).OPTIONAL (recommended) means missing values become null instead of throwing an error. REQUIRED means the model fails if any value is missing.Injects built-in AEM objects that are normally available in HTL or JSP. These are objects that AEM creates automatically for every page request. You just need to declare them in your model.
@ScriptVariable
private Page currentPage;
Common @ScriptVariable objects:
Note: @ScriptVariable requires SlingHttpServletRequest.class in the adaptables list.
Injects a property value directly from the component’s dialog (stored in the resource’s ValueMap).
@ValueMapValue
private String textAlign;
The field name (textAlign) must match the property name in the component dialog (./textAlign). If the names differ, use @ValueMapValue(name = “dialogPropertyName”).
With a default value:
@ValueMapValue
@Default(values = "left")
private String textAlign;
A method annotated with @PostConstruct runs automatically after all injections are complete. Use it for initialization logic, transformations, or computed values.
private String formattedDateTime;
@PostConstruct
protected void init() {
// Set default value if not provided in dialog
if (textAlign == null || textAlign.isEmpty()) {
textAlign = "left";
}
// Access page properties
Date lastModified = currentPage.getProperties().get("cq:lastModified", new Date(0));
// Perform transformations
SimpleDateFormat formatter = new SimpleDateFormat("MMMMM d, yyyy, h:mm aa");
formattedDateTime = "Last updated: " + formatter.format(lastModified);
}
Best practices:
Expose model values to HTL. The getter name determines the HTL expression.
public String getTextAlign() {
return textAlign;
}
public String getFormattedDateTime() {
return formattedDateTime;
}
In HTL, these are accessed as:
<sly data-sly-use.lastUpdated="org.concordia.wcms.core.models.content.LastUpdatedModel"/>
<p class="text-large" style="text-align: ${lastUpdated.textAlign @ context='styleToken'};">
<span>${lastUpdated.formattedDateTime @ context='html'}</span>
</p>
Naming convention: getPropertyName() -> accessed as ${model.propertyName} in HTL.
<!--/* My comment */-->
Include this at the top of the HTL file:
<sly data-sly-use.mediaPlayer="org.concordia.wcms.core.models.media.MediaPlayerModel"
data-sly-use.clientlib="/libs/granite/sightly/templates/clientlib.html"
>
<sly data-sly-call="${clientlib.all @ categories='apps.concordia.media-player'}"/>
Examples:
<li class="c-anchor-navigation__list-item c-anchor-navigation__list-item--${model.linkColor}">
<a href="#${link.path}" aria-label="${link.ariaLabel @ context='attribute'}">${link.text}</a>
</li>
<iframe id="${model.instanceId @ context='attribute'}"
src="${model.src @ context='uri'}"
width="100%"
height="${model.height @ context='attribute'}"
title="${model.title @ context='attribute'}"
></iframe>
<script>
iFrameResize({scrolling: 'omit'}, "#${model.instanceId @ context='scriptString'}");
</script>
<span>${lastUpdated.formattedDateTime @ context='html'}</span>
<sly data-sly-list.source="${mediaPlayer.mediaSourceList}" data-sly-unwrap>
${source @ context='unsafe'}
</sly>
<sly data-sly-test="${wcmmode.edit}">
<img src="/libs/cq/ui/resources/0.gif" class="cq-anchor-placeholder" alt="Anchor Link component">
</sly>
<div data-sly-test="${condition1 && condition2 && condition3}">
<p>All three conditions are true</p>
</div>
<div data-sly-test="${condition1 || condition2}">
<p>At least one condition is true</p>
</div>
<sly data-sly-include="sections/awards-block.html"></sly>
<ul data-sly-list.item="${model.items}">
<li>
Index: ${itemList.index}<br>
Count: ${itemList.count}<br>
First: ${itemList.first}<br>
Last: ${itemList.last}<br>
Value: ${item.name}
</li>
</ul>
data-sly-unwrap - Removes the
<sly data-sly-list.source="${mediaPlayer.mediaSourceList}" data-sly-unwrap>
${source @ context='unsafe'}
</sly>
<div data-sly-attribute.class="${button.centered ? 'text-center' : 'text-left'}">
<a href="${button.processedLink}"
target="${button.processedTarget}"
class="${button.buttonClasses}"
style="${button.inlineStyle @ context='styleString'}"
data-sly-attribute.aria-label="${button.hasAriaLabel ? button.processedAriaLabel : null}"
>
<span>${button.processedLabel @ context='html'}</span>
</a>
</div>
<!-- The link element is rendered only if the value is truthy (not empty/null/false) -->
<a data-sly-test="${properties.anchorLink}" id="${properties.anchorLink}"></a>
<!-- The content inside <sly> is rendered only if the condition is true -->
<sly data-sly-test="${wcmmode.edit}">
<img src="/libs/cq/ui/resources/0.gif" class="cq-anchor-placeholder" alt="Anchor Link component">
</sly>
Follow Bootstrap, Vue.js, and jQuery releases and updates