Reusable Thymeleaf Fragments: th:fragment, th:replace, and Parameterized Components
Define markup once with th:fragment, include it with th:replace, and pass data through positional parameters. A worked Spring Boot example plus the insert/replace difference, dynamic selection, and common mistakes.
01 Aug 2026, 23:57 UTC

Duplicated header, footer, and card markup across Thymeleaf templates is the problem fragments solve. The useful answer up front: define a block once with th:fragment, include it with th:replace (almost always the right choice over th:insert), and pass data in through positional parameters so the fragment stays decoupled from the page's model. This gives you a lightweight component model with no extra dependencies.
Defining and including a fragment
In a Spring Boot application, Thymeleaf's default template resolver looks in classpath:/templates/ with a .html suffix. Create templates/components.html:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<div th:fragment="card(title, body)" class="card">
<h2 th:text="${title}">Title</h2>
<p th:text="${body}">Body</p>
</div>
</body>
</html>
Reference it from any page using a fragment expression, ~{template :: fragment}:
<div th:replace="~{components :: card('Welcome', ${user.name})}"></div>
The reference components :: card(...) resolves to templates/components.html and the card fragment inside it. No extra configuration is needed. Arguments are positional: 'Welcome' binds to title, ${user.name} binds to body. Because parameters are passed explicitly, the fragment doesn't depend on whatever variables happen to be in the including page's context, which makes it reusable and easy to reason about.
th:replace vs th:insert
The difference is what happens to the host tag:
th:replaceremoves the host tag entirely and puts the fragment in its place.th:insertkeeps the host tag and places the fragment inside it.
With the example above, th:replace renders just the card <div>. th:insert would render the host <div> with the card <div> nested inside it. For layout components, that extra wrapper accumulates quickly and can break CSS selectors or grid layouts. A subtler bug: using th:insert on a structural element can produce invalid HTML that browsers silently repair, shifting your layout in ways that are painful to debug. Default to th:replace; reach for th:insert only when you deliberately want the wrapper.
Dynamic fragment selection
The template part of the fragment expression can itself be a variable:
<div th:replace="~{${themeTemplate} :: main}"></div>
If your controller puts themeTemplate = "themes/dark" in the model, Thymeleaf resolves templates/themes/dark.html. This is useful for tenant-specific or theme-specific partials chosen at runtime. Keep the set of possible values constrained (an enum or whitelist in the controller) so a bad value fails predictably at render time rather than resolving something unintended.
Verifying the behavior
Three quick checks you can run in a dev environment:
- Create the
cardfragment above, render a page that uses it, and view the response HTML (browser dev tools orcurl http://localhost:8080/your-page). Confirm the card markup appears with the passed values. - Render the same fragment once with
th:insertand once withth:replace, and diff the output. The wrapper-tag difference becomes obvious immediately. - Rename a parameter in the fragment signature (e.g.,
card(title, content)) without updating the fragment body, reload, and confirm Thymeleaf throws a template processing error. Parameter mismatches fail at render time, not at startup.
Limits and common mistakes
- Positional, loosely typed signatures. Reordering or renaming parameters silently breaks every caller. Keep signatures small (two or three parameters) and stable. If a fragment needs many inputs, pass one object instead.
- Error messages point at the including template. With deep
th:replacechains, a failure inside a fragment is reported against the page that included it, which makes tracing harder. Keep nesting shallow and fragment files small. - Version syntax. The
~{...}fragment expression is Thymeleaf 3 syntax. Thymeleaf 2 used a different form (th:replace="components :: card"without the~{}). Mixing guides written for different major versions causes parse errors. Check yourthymeleaf-spring6/Spring Boot version before copying examples; Spring Boot 3 uses Thymeleaf 3.1. - Full page layouts. Plain fragments cover headers, footers, and components well. If you want decorator-style page inheritance (a base layout with overridable sections), the community Layout Dialect (
nz.net.ultraq.thymeleaf) adds that, but it's an extra dependency most projects don't need.
The practical rule: one components.html (or a small set of fragment files by area), th:replace by default, explicit parameters, shallow nesting. That covers the large majority of server-side markup reuse without any additional tooling.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.