o7planning

Thymeleaf Page Layouts Tutorial with Examples

  1. The objective of the lesson
  2. Example: Thymeleaf Layout

1. The objective of the lesson

Page Layout refers to the arrangement of text, images and other objects on a page, and it is the most intested matter of website designers. In this lesson, I am going to introduce the technique used in Thymeleaf to create a Layout.
Thymeleaf uses fragments to put together to form a page, therefore, you should learn about fragment before continuing with this lesson.
A website can contain a lot of pages but pages have the same structure. for example, below is a simple structure:
Based on the above structure, you can create different pages:

2. Example: Thymeleaf Layout

Below is an image of an application, which is written on Spring Boot. Don't worry, if you use another framework, the project structure may be slightly different, but it is not difficult for you to understand the steps I am performing here.
Create 2 files such as main.css & main-layout.html:
The main-layout.html file contains 1 fragment containing 6 parameters. These parameters help shape the full interface of a page.
layouts/main-layout.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org"
    th:fragment="main-fragment (title, otherStaticResources, header, nav, mainContent, footer)">
<head>
<meta charset="UTF-8" />
<title th:replace="${title}">Page 1 Title</title>
<link rel="stylesheet" type="text/css" th:href="@{/main.css}" href="../../static/main.css"/>
<!-- Other javascript, css source files -->
<th:block th:replace="${otherStaticResources} ?: ~{}"></th:block>
</head>
<body>
    <header th:insert="${header} ?: ~{}">
        <h2>Page 1</h2>
    </header>
    <section>
        <nav th:insert="${nav} ?: ~{}">
            <ul>
                <li><a href="#">Page 1</a></li>
                <li><a href="#">Page 2</a></li>
            </ul>
        </nav>
        <article th:insert="${mainContent} ?: ~{}">
            <h1>Page 1</h1>
            <p>Main content of Page 1</p>
        </article>
    </section>
    <footer th:insert="${footer} ?: ~{}">
        <p>Footer</p>
    </footer>
</body>
</html>
main.css
* {
    box-sizing: border-box;
}
header {
    background-color: #666;
    padding: 5px;
    text-align: center;
    font-size: 35px;
    color: white;
}
nav {
    float: left;
    width: 30%;
    height: 300px;
    background: #ccc;
    padding: 20px;
}
nav ul {
    list-style-type: none;
    padding: 0;
}
article {
    float: left;
    padding: 20px;
    width: 70%;
    background-color: #f1f1f1;
    height: 300px;
}
section:after {
    content: "";
    display: table;
    clear: both;
}
footer {
    background-color: #777;
    padding: 10px;
    text-align: center;
    color: white;
}
@media ( max-width : 600px) {
    nav, article {
        width: 100%;
        height: auto;
    }
}
Open the main-layout.html file directly on the browser and you can see its interface.
The app-fragments.html file contains fragments reused in the different Templates of the application.
fragments/app-fragments.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>App Fragments</title>
</head>
<body>
    <!-- Default Navigator -->
    <ul th:fragment = "nav-default">
        <li><a th:href="@{/}">Home</a></li>
        <li><a th:href="@{/products}">Products</a></li>
        <li><a th:href="@{/about}">About</a></li>
    </ul>
    <!-- Admin Navigator -->
    <ul th:fragment = "nav-admin">
        <li><a th:href="@{/admin/products}">Product Management</a></li>
        <li><a th:href="@{/admin/orders}">Order Management</a></li>
    </ul>
    <div th:fragment="copyright">
      &copy; o7planning.org
    </div>
</body>
</html>
Create other pages for your application:
  • home.html, products.html, about.html.
The home.html file uses Layout difined by the main-layout.html file, and pass values for parameters.
home.html
<!DOCTYPE html>
<!--  main-fragment (title, otherStaticResources, header, nav, mainContent, footer)  -->
<html xmlns:th="http://www.thymeleaf.org"
      th:replace="~{layouts/main-layout :: main-fragment(  
                                                ~{::title},
                                                ~{:: #home-static-resources},
                                                ~{:: #home-header},
                                                ~{:: #home-nav},
                                                ~{:: #home-main-content},  
                                                ~{:: #home-footer}
                                               )}">
                                              
<head>
    <title>Title of Home Page</title>
    <th:block id="home-static-resources">
        <script type="text/javascript" src="../static/home.js" th:src="@{/home.js}"></script>
        <link rel="stylesheet" type="text/css" href="../static/home.css" th:href="@{/home.css}"/>
    </th:block>
</head>
<body>
    <div id="home-header">
        Header of Home Page
    </div>
    <div id="home-nav" th:replace="~{/fragments/app-fragments :: nav-default}">
        Home Nav
    </div>
    <div id="home-main-content">
        <h2>Home content</h2>
        <div>Content of Home Page</div>
    </div>
    <div id="home-footer" th:replace="~{/fragments/app-fragments :: copyright}">
        Footer
    </div>
</body>
</html>
products.html
<!DOCTYPE html>
<!--  main-fragment (title, otherStaticResources, header, nav, mainContent, footer)  -->
<html xmlns:th="http://www.thymeleaf.org"
      th:replace="~{layouts/main-layout :: main-fragment(  
                                                ~{::title},
                                                ~{:: #products-static-resources},
                                                ~{:: #products-header},
                                                ~{:: #products-nav},
                                                ~{:: #products-main-content},  
                                                ~{:: #products-footer}
                                               )}">
                                              
<head>
    <title>Title of Products Page</title>
    <th:block id="products-static-resources">  
       <script type="text/javascript" src="../static/products.js" th:src="@{/products.js}"></script>
    </th:block>
</head>
<body>
    <div id="products-header">
        Header of Products Page
    </div>
    <div id="products-nav" th:replace="~{/fragments/app-fragments :: nav-default}">
         Nav
    </div>
    <div id="products-main-content">
        <h2>Product</h2>
        <p>Samsung</p>
        <p>iPhone</p>
        <p>Nokia</p>
    </div>
    <div id="products-footer" th:replace="~{/fragments/app-fragments :: copyright}">
        Footer
    </div>
</body>
</html>
about.html
<!DOCTYPE html>
<!--  main-fragment (title, otherStaticResources, header, nav, mainContent, footer)  -->
<html xmlns:th="http://www.thymeleaf.org"
      th:replace="~{layouts/main-layout :: main-fragment(  
                                                ~{::title},
                                                ~{:: #about-static-resources},
                                                ~{:: #about-header},
                                                ~{:: #about-nav},
                                                ~{:: #about-main-content},  
                                                ~{:: #about-footer}
                                               )}">
                                              
<head>
    <title>Title of About Page</title>
    <th:block id="about-static-resources">
        <script type="text/javascript" src="../static/about.js" th:src="@{/about.js}"></script>
    </th:block>
</head>
<body>
    <div id="about-header">
        Header of About Page
    </div>
    <div id="about-nav" th:replace="~{/fragments/app-fragments :: nav-default}">
         Nav
    </div>
    <div id="about-main-content">
        <h2>About</h2>
        <div>Content of About Page</div>
    </div>
    <div id="about-footer" th:replace="~{/fragments/app-fragments :: copyright}">
        Footer
    </div>
</body>
</html>
MainController.java
package org.o7planning.thymeleaf.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class MainController {
    @RequestMapping("/")
    public String home() {
        return "home";
    }
    @RequestMapping("/products")
    public String products() {
        return "products";
    }
    @RequestMapping("/about")
    public String about() {
        return "about";
    }
}