Templating with Thymeleaf: Fragments and Reusability (Part 2)

 · 9 min

Welcome back to my Thymeleaf series! In Part 1, we explored the basic syntax and core features.

Now, it’s time to dive into one on Thymeleaf’s most powerful aspects: layout management and reusability. These concepts are essential for creating maintainable and efficient templates.

Let’s get started!


What Are Fragments?

Templates can be broken into reusable parts called fragments.

Fragments can be defined in separate template files or in the same template they’re used in. This is useful for modularizing sections to be reused across multiple templates.

Why Use Fragments?

  • Code Reusability
    Reuse sections of a template, like menus, footers without duplicating code. You could even create a library of commonly used UI components.

  • Maintainability
    When certain structures, content or design changes, only a single fragment needs to be changed to affect all the places it’s used.

  • Cleaner Templates
    Templates break down into more manageable, smaller logical parts with fragments.

  • Separation of Concerns
    Fragments can make the overall development process more modular and easier to manage.

Basic Fragment Usage

To define a fragment, we use the th:fragment attribute:

Creating a Fragment
<nav th:fragment="navigation" class="navbar">
  <a th:href="@{/home}" class="nav-link">Home</a>
  <a th:href="@{/profile}" class="nav-link">Profile</a>
</nav>

The fragment, identified by navigation, exists in the file header.html, and includes the whole <nav> element with all its attributes and child elements. It can either be placed in its own file, like nav.html, or be a part of current template.

Putting templates into their own file, or grouping them logically into files (e.g., different button fragments in buttons.html) decouples them more clearly from their surrounding and creates a more straightforward library of reusable components.

Now that we’ve defined a fragment, let’s look at how to reference it.

Referencing a Fragment

To use it in another template, it’s referenced by a fragment expression.

There are three types based around the template’s name and the fragment selector:

  • External Fragment: ~{templatename :: selector}

  • Local Fragment: ~{:: selector} or ~{this :: selector}

  • Complete Template: ~{templatename}

With such an expression, a fragment can either be inserted into another element, or replace it completely.

The th:insert attribute inserts the fragment and keeps the surrounding element intact:

Inserting a Fragment
<body>
  <div th:insert="fragments/nav :: navigation" class="nav-wrapper"></div>
  <h2>Welcome to Thymeleaf</h2>
</body>

This will create the following output:

Output
<body>
  <div class="nav-wrapper">
    <nav class="navbar">
      <a href="/myawesomeapp/home}" class="nav-link">Home</a>
      <a href="/myawesomeapp/profile}" class="nav-link">Profile</a>
    </nav>
  </div>
  <h2>Welcome to Thymeleaf</h2>
</body>

To replace an element with a fragment, the aptly named th:replace argument is used:

Replacing a Fragment
<body>
  <div th:replace="fragments/nav :: navigation" class="nav-wrapper"></div>
  <h2>Welcome to Thymeleaf</h2>
</body>

The <div> elements gets completely replaced by the <nav> element from the fragment.

Referencing Non-Fragments by ID

The selector part of a fragment expression supports more than th:fragment values. Referencing elements directly by their id attribute is possible, too, similar to a CSS selector:

Referencing non-Fragment
<div id="copy-section">
  &copy; 2024 belief-driven-design.com
</div>

<div th:replace="~{this :: #copy-section}"></div>

Parameterized Fragments

Fragments aren’t just static blocks of reusable text; they can be dynamic like any other template and accept parameters, making them even more reusable across various contexts.

The th:fragment value accepts comma-separated variable names to be used in the fragment:

Parameterized Fragment
<button
  th:fragment="button (btnType,label)"
  type="button"
  class="btn"
  th:classappend="${btnType}">
  <span th:text="${label}">Button label</span>
</button>

The parameters are passed in parentheses after the selector when a fragment is referenced:

Selectors
<!-- IMPLICIT NAMES -->
<div th:replace="::button ('btn-primary','Login')"></div>

<!-- EXPLICIT NAMES -->
<div th:replace="::button (btnType='btn-primary',label='Login')"></div>

<!-- WITH VARIABLES FROM CONTEXT -->
<div th:replace="::button (btnType=${typeValue},label=${btnLabel})"></div>

Layout Dialect: Streamline with Base Templates

Fragments are already a powerful tool to reuse partial templates. But their purpose it to be included in fragments explicitly, not creating extension points to be filled later.

However, there’s a way to do this with another Dialect.

In Thymeleaf, a Dialect is a collection of custom processors, expression objects, and utility methods extending the basic templating capabilities.

The Layout Dialect is an official on that improves Thymeleaf’s layout capabilities by providing base templates which invert the direction of control of how content is included. This enables the creation of more complex yet easily reusable layouts.

Required Dependency and Setup

To use it, we need to add the corresponding dependency and register the dialect with the template engine.

For Maven, add the following to your pom.xml:

pom.xml
<dependency>
  <groupId>nz.net.ultraq.thymeleaf</groupId>
  <artifactId>thymeleaf-layout-dialect</artifactId>
  <version>3.3.0</version>
</dependency>

For Gradle, add this to your build.gradle:

build.gradle
implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:3.3.0'

To add the dialect to the engine, the following is necessary:

Adding the Dialect
var layoutDialect = new LayoutDialect();
templateEngine.addDialect(layoutDialect);

That’s it!

Creating a Base Template

With the dependency added and the template engine knowing about the dialect, we can use the layout namespace to create a base layout:

Base template
<!-- layout.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" 
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">

<head>
  <title layout:title-pattern="$LAYOUT_TITLE - $CONTENT_TITLE">My Awesome App</title>
</head>

<body>
  <header th:replace="~{fragments/shared :: header}"></header>

  <main layout:fragment="content">
    <!-- Extension point for Layout Dialect-->
  </main>

  <footer th:replace="~{fragments/shared :: footer}"></footer>
</body>

</html>

This base template uses multiple layout features:

  • xmlns:layout: Needed namespace specification for the layout:... attributes to work.

  • layout:title-pattern: This combines the base layout’s <title> and the content template’s title in the specified pattern.

  • th:replace: Fragments for header and footer.

  • layout:fragment: Marker for the reusable section in the template.

The page-specific content will replace the inner content of the layout:fragment element. If not replaced, the element will provide a sensible default content for the base template.

Extending the Base Template

Now that we have a base template, it’s time to create a template decorating it:

Decorating a base template
<!DOCTYPE html>
<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="~{layout}">
<head>
  <title>Home</title>
</head>

<body>
  <div layout:fragment="content">
    <h1>Welcome to My Awesome App</h1>
    <p>
      Lorem ipsum dolor sit amet, consetetur sadipscing elitr,
      sed diam nonumy eirmod tempor invidunt ut labore et dolore
      magna aliquyam erat, sed diam voluptua.
    </p>
  </div>
</body>
</html>

The key here is layout:decorate.

The attribute is used to look up the base template and indicates that the current template extends or decorates a base layout. Any placeholders marked with layout:fragment are replaced by corresponding fragments from the extending template, resulting in the following output:

Decoration source
<!DOCTYPE html>
<html>

<head>
  <title>My Awesome App - Home</title>
</head>

<body>
  <header>...</header>

  <main>
    <h1>Welcome to My Awesome App</h1>
    <p>
      Lorem ipsum dolor sit amet, consetetur sadipscing elitr,
      sed diam nonumy eirmod tempor invidunt ut labore et dolore
      magna aliquyam erat, sed diam voluptua.
    </p>
  </main>

  <footer>...</footer>
</body>

</html>

This feature allows the extending template to focus only on the unique content while reusing the structure and layout defined in the base template.

Key Advantages of Using the Layout Dialect

The Layout Dialect offers several powerful advantages, especially when working on larger projects with more complex layouts:

  • Template Inheritance and Structural Consistency
    Base templates that can be extended by other templates promote the DRY (Don’t Repeat Yourself) principle by keeping common structures in one place.

  • Modularity and Maintainability
    By decoupling common layout (structure) from the individual page content, the layout dialect and fragments improve code modularity immensely. We don’t need the full base layout in every template, and every commonly used component can be refactored to fragment.

  • Default Content
    Layout fragments do not need to be provided in an extended base template. Any extension is optional and can be filled with a sensible default, and replaced if needed.

When (not) to use the Layout Dialect

Use the Layout Dialect when:

  • You have a large project with many pages sharing a common layout or elements
  • You need a consistent look and feel across all templates
  • Your templates have a base structure but require dynamic content sections
  • You want to enface clean separation of structure and content

These are all good reasons to use the dialect. However, if you’re dealing with a small and simple project that doesn’t have many shared elements, introducing the dialect would make things more complex than needed.


Strategies for Maximizing Reusability

As projects grow, reusability becomes a crucial aspect of ensuring maintainability.

Here are some strategies to maximize reusability when working with Thymeleaf:

Modularize by Functionality or Purpose

Multiple fragments can be grouped in a single file, so grouping them by their function or purpose organizes them logically and makes them easier to discover.

Logical grouping improves maintainability and reduces clutter in the codebase. This way, if we need to update your navigation bar, we know exactly where to look.

Leverage Parameterized Fragments

Use parameterized fragments to add dynamic behavior to your components. This reduces the need for creating multiple fragments for similar tasks and allows to keep templates DRY.

Layout Dialect for Structural Consistency

Using the Layout Dialect simplifies the management of common structures and ensures consistency across all templates.

By defining a base template that includes headers, footers, and common layout elements, we can reduce code duplication and ensure that your site has a unified look and feel.

And don’t forget that layout fragments don’t necessarily need to be replaced; utilize default content where appropriate.

Document Fragments

Like with any code, clear documentation is awesome to have; maybe not right now, but your future self will thank you eventually.

Adding HTML comments right next to a (non-layout) fragment won’t affect the output.

Well-documented fragments make them more maintainable and accessible for collaborators, reducing the learning curve and potential misuse.


Conclusion

In this part of the Thymeleaf series, we’ve explored the power of fragments and how they enable reusability and modularity. By leveraging them, you can create reusable components for common elements like headers, footers, and navigation bars, while passing parameters and applying conditional logic for more dynamic content.

Another great addition to more complex projects is the Layout Dialect, which makes it easier to build consistent page structures using template inheritance.

Thymeleaf fragments and the Layout Dialect can make a huge difference in building clean, maintainable templates. By centralizing common components like headers and footers, using parameterized fragments and using base layouts, you can create a robust foundation for any project.

In the final part of this series, we’ll dive into creating our own custom dialect and some of the other more advanced Thymeleaf features.

Stay tuned!


A Functional Approach to Java Cover Image
Interested in using functional concepts and techniques in your Java code?
Check out my book!
Available in English, Polish, Korean, and soon, Chinese.

Resources

Series: Templating with Thymeleaf