HTL Tutorial #12: data-sly-unwrap - Removing HTML Wrappers

HTL Tutorial #12: data-sly-unwrap - Removing HTML Wrappers

What is data-sly-unwrap?

data-sly-unwrap removes the HTML element but keeps its content.

Useful for eliminating unnecessary wrappers in the final output.

Syntax

<element data-sly-unwrap>
  Content kept
</element>

Output:

Content kept

The <element> is removed!

Basic Example

<div data-sly-unwrap>
  <p>This paragraph remains</p>
</div>

Output:

<p>This paragraph remains</p>

The <div> was removed, the <p> remains.

Why Use It?

1. Technical Wrappers in Edit Mode

In AEM, components often have wrappers for authoring that aren't needed in publish:

<div data-sly-unwrap="${wcmmode.disabled}">
  <p>${properties.text}</p>
</div>

In Edit Mode (wcmmode.disabled = false):

<div>
  <p>Component text</p>
</div>

In Publish Mode (wcmmode.disabled = true):

<p>Component text</p>

The <div> wrapper is removed in publish!

2. Clean Markup

Avoid unnecessary <div> elements in the DOM:

<!-- Component -->
<div data-sly-unwrap="${!properties.showContainer}">
  <h2>${properties.title}</h2>
  <p>${properties.content}</p>
</div>

If showContainer = false:

<h2>Title</h2>
<p>Content</p>

If showContainer = true:

<div>
  <h2>Title</h2>
  <p>Content</p>
</div>

3. Conditional Wrapper

<div data-sly-unwrap="${!properties.addWrapper}"
     class="card">
  <h3>${properties.title}</h3>
  <p>${properties.text}</p>
</div>

The author decides whether to add the .card wrapper or not!

Conditional data-sly-unwrap

data-sly-unwrap accepts a boolean expression:

<element data-sly-unwrap="${condition}">
  Content
</element>
  • true: Removes element
  • false: Keeps element

Example with WCM Mode

<!-- Wrapper only in edit mode -->
<sly data-sly-unwrap="${wcmmode.disabled}">
  <div class="component-wrapper">
    <div data-sly-resource="${'parsys' @ resourceType='wcm/foundation/components/parsys'}"></div>
  </div>
</sly>

Common Patterns

1. Conditional List

<ul data-sly-unwrap="${properties.inline}">
  <li data-sly-list.item="${properties.items}">
    ${item}
  </li>
</ul>

If inline = false (normal list):

<ul>
  <li>Item 1</li>
  <li>Item 2</li>
</ul>

If inline = true (inline list, without <ul>):

<li>Item 1</li>
<li>Item 2</li>

2. Wrapper Only for Multiple Elements

<div data-sly-unwrap="${itemList.count <= 1}">
  <div data-sly-list.item="${properties.items}">
    ${item}
  </div>
</div>

1 element (unwrap):

<div>Single item</div>

Multiple elements (keep wrapper):

<div>
  <div>Item 1</div>
  <div>Item 2</div>
  <div>Item 3</div>
</div>

3. Conditional Semantic HTML

<section data-sly-unwrap="${!properties.isSection}">
  <h2>${properties.title}</h2>
  <p>${properties.content}</p>
</section>

The author decides whether to use semantic <section> or just content.

The <sly> Element

HTL provides a special <sly> element that is always unwrapped automatically:

<sly>
  <p>Content</p>
</sly>

Output:

<p>Content</p>

<sly> is perfect for:

  • Template definitions
  • HTL logic without output
<!-- Template without wrapper -->
<sly data-sly-template.myTemplate="${@ text}">
  <p>${text}</p>
</sly>

<!-- List without wrapper -->
<sly data-sly-list.item="${items}">
  <div>${item}</div>
</sly>

Difference Between <sly> and data-sly-unwrap

Feature <sly> data-sly-unwrap
Unwrap Always Conditional
Usage Logical wrapper Conditional wrapper
Expression No Yes (true/false)
Attributes Not in output Removed with element
<!-- ALWAYS removed -->
<sly>
  Content
</sly>

<!-- CONDITIONALLY removed -->
<div data-sly-unwrap="${condition}">
  Content
</div>

Complete Example: Card Component

<div data-sly-use.card="com.example.CardModel"
     data-sly-unwrap="${card.unwrapContainer}"
     class="card ${card.styleClass}">

  <!-- Conditional header -->
  <div data-sly-unwrap="${!card.hasHeader}" class="card-header">
    <h3>${card.title}</h3>
  </div>

  <!-- Body -->
  <div class="card-body">
    <p>${card.content}</p>
  </div>

  <!-- Conditional footer -->
  <div data-sly-unwrap="${!card.hasFooter}" class="card-footer">
    <a href="${card.linkUrl}">${card.linkText}</a>
  </div>
</div>

Model:

public class CardModel {

    @ValueMapValue
    private String title;

    @ValueMapValue
    private String content;

    @ValueMapValue
    private String linkUrl;

    @ValueMapValue
    private String linkText;

    @ValueMapValue
    private boolean removeContainer;

    @ValueMapValue
    private String styleClass;

    public boolean getHasHeader() {
        return title != null && !title.isEmpty();
    }

    public boolean getHasFooter() {
        return linkUrl != null && !linkUrl.isEmpty();
    }

    public boolean getUnwrapContainer() {
        return removeContainer;
    }

    // Other getters...
}

Output (with container, without footer):

<div class="card featured">
  <div class="card-header">
    <h3>Card Title</h3>
  </div>
  <div class="card-body">
    <p>Card content</p>
  </div>
</div>

Output (without container, with footer):

<div class="card-header">
  <h3>Card Title</h3>
</div>
<div class="card-body">
  <p>Card content</p>
</div>
<div class="card-footer">
  <a href="/link">Read more</a>
</div>

Best Practices

  1. Use <sly> for always-removed wrappers
  2. data-sly-unwrap for conditions: dynamic wrappers
  3. Clear naming: variables like unwrapContainer, removeWrapper
  4. Semantic HTML: Maintain semantic markup when possible
  5. Accessibility: Don't remove elements necessary for screen readers
  6. Testing: Test both cases (wrapped/unwrapped)

Combination with Other Statements

With data-sly-test

<!-- Show only if there's content, without wrapper -->
<div data-sly-test="${properties.content}"
     data-sly-unwrap>
  ${properties.content}
</div>

With data-sly-list

<div data-sly-unwrap="${properties.removeWrapper}">
  <div data-sly-list.item="${properties.items}">
    ${item}
  </div>
</div>

With data-sly-element

<!-- Change tag OR remove completely -->
<div data-sly-element="${properties.tag}"
     data-sly-unwrap="${!properties.tag}">
  Content
</div>

Common Mistakes

❌ Unwrap with necessary attributes

<!-- WRONG - loses the id! -->
<div id="important" data-sly-unwrap>
  Content
</div>

<!-- CORRECT - unwrap only if id not needed -->
<div id="${properties.id}"
     data-sly-unwrap="${!properties.id}">
  Content
</div>

❌ Forgetting condition

<!-- Always unwrapped (like <sly>) -->
<div data-sly-unwrap>

<!-- Conditional -->
<div data-sly-unwrap="${condition}">

Practical Exercises

  1. Conditional Container: Component with optional wrapper
  2. Edit/Publish Mode: Wrapper only in edit
  3. List Wrapper: List with conditional <ul>
  4. Semantic Sections: Optional section tag

Next Lesson

In the next lesson we'll see data-sly-include and data-sly-resource for including other templates and components.


Lesson #12 of the HTL Tutorial series. ← Previous lesson | Next lesson →