code-guidelines

Code guidelines

Coding guidelines, best practices, and the style guide to help ensure consistency and accuracy across the entire project.

Table of contents

  1. General
  2. CSS
  3. HTML
  4. JavaScript
  5. AEM
  6. Useful resources
  7. References


General

// Force black colour on links for dark backgrounds
switch (bgColor) {
    case "C8C8C8":
        btnColor = "btn-simple-black";
        break;
    ...
}

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";
}


⬆ Back to top


CSS

Formatting

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;
}


CSS anatomy

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;
}


Property order

A consistent ordering of CSS rules facilitates a quicker scan of a declaration block.

  1. GENERATED CONTENT
    • content
  2. POSITION AND LAYOUT
    • position z-index top bottom left right float clear
  3. DISPLAY AND VISIBILITY
    • display visibility opacity flex-direction flex-wrap flex-flow justify-content align-items align-content order flex-grow flex-shrink flex-basis gap flex transform
  4. CLIPPING
    • overflow clip
  5. ANIMATION
    • animation transition
  6. BOX MODEL (FROM OUTSIDE IN)
    • margin 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 padding
  7. BACKGROUND
    • background cursor
  8. TYPOGRAPHY
    • font-size line-height font-family font-weight font-style text-align text-transform word-spacing letter-spacing color
  9. PSEUDO-CLASSES & PSEUDO-ELEMENTS (NESTED RULES)
    • :hover :focus :active ::before ::after :first-child :last-child


Breakpoints and media queries

Follow 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>


Namespacing

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.


LESS

Use reference imports

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");

Nesting selectors

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
                    }
                }
            }
        }
    }
}

Comments

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.

Using the calc() function in LESS

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)';

SVG strings in the background property

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");


BEM (Block Element Modifier)

It’s encouraged to use BEM for the following reasons:

The BEM naming convention is composed of three parts:

  1. Block: The outermost parent element of the component is defined as the block.
  2. Element: Inside of the component may be one or more children called elements.
  3. Modifier: Either a block or element may have a variation signified by a modifier.

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
    }
}

BEM is not intended to communicate structural depth

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;
}

BEM examples

BEM references


Bootstrap

Useful utility classes

<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>


Tips and best practices

Font-family fallback

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;

Border

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;
}

Line height

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;

Margin collapse

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.

Box-sizing

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;
}

Pseudo-classes and pseudo-elements

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;
}

Document icons

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>


⬆ Back to top


HTML

General

HTTPS

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";

HTML line-wrapping

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>

HTML quotation marks

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>


Accessibility

Aria-hidden

Add the aria-hidden attribute to elements that should be ignored by screen readers.

<span class="separator" aria-hidden="true">|</span>

Modify what is shown and what is read by screen readers

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>

Empty alt value

Include an empty alt value to images that are decorative or have a decorative purpose.

<img src="image.png" alt="">


⬆ Back to top


JavaScript

General


jQuery

Cache jQuery lookups to improve performance

Bad

function setSidebar() {
    $(".sidebar").hide();
    $(".sidebar").css("background-color", "pink");
}

Good

function setSidebar() {
    const sidebarEl = $(".sidebar");
    sidebarEl.hide();
    sidebarEl.css("background-color", "pink");
}

Helpful resource for converting jQuery to vanilla JavaScript

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.


⬆ Back to top


AEM

General

Dialog value names

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>

_cq_htmlTag node properties to define component's top-level CSS class

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">

cq_htmlTag

<?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.

Disable child components with “cq:isContainer”

In the dialog, add the cq:isContainer="{Boolean}false" property to explicitly declare that this component cannot contain other components as children.

New and converted components

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.

XSS API

For new code, use org.apache.sling.xss.XSSAPI (the Sling API), not the deprecated com.adobe.granite.xss.XSSAPI (the Adobe wrapper).

JSP session and includes

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...

JSP scriptlet tag position

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">
        ...

JSP expression spacing

Always put a space between the expression scriptlet tags and the variable.

<a class="btn <%= buttonBlock + " " + buttonSize %>" href="<%= link %>" target="<%= target %>">
    <%= label %>
</a>

JSP useful functions

// 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);

Constant-first .equals() comparisons

When 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)) { ... }


Sling

Sling Model file structure

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;

@Model annotation

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:

@ScriptVariable annotation

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.

@ValueMapValue annotation

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;

@PostConstruct annotation

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:

Getter methods

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.


HTL

Silent HTL comment

<!--/* My comment */-->

Use a specific Sling Model and include a Client Library

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'}"/>

Specific context types in HTL

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>

HTL’s built-in WCM mode detection

<sly data-sly-test="${wcmmode.edit}">
    <img src="/libs/cq/ui/resources/0.gif" class="cq-anchor-placeholder" alt="Anchor Link component">
</sly>

“AND” and “OR” conditions

<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>

Include another HTL file with “data-sly-include”

<sly data-sly-include="sections/awards-block.html"></sly>

List iteration with index and item properties

<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>

Clean HTML with “data-sly-unwrap”

data-sly-unwrap - Removes the wrapper element from the final rendered HTML and generates clean HTML.

<sly data-sly-list.source="${mediaPlayer.mediaSourceList}" data-sly-unwrap>
    ${source @ context='unsafe'}
</sly>

Dynamic attributes with “data-sly-attribute”

<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>

Conditional rendering

<!-- 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>


⬆ Back to top


Useful resources

Follow Bootstrap, Vue.js, and jQuery releases and updates


⬆ Back to top


References


⬆ Back to top