Build a Web application with Spring Boot and Tailwind CSS

Photo by Luca Bravo / Unsplash

Building applications with a good look and feel is paramount for a better user experience. CSS is the language used to style a web page, and the more you want a great design, the more work you will do.

We can use various CSS libraries and frameworks to quickly build the web page's design. One of the most popular frameworks today is Tailwind CSS.

It is easier to perform the tedious styling tasks like organizing the content, adding animation, and doing responsive design.

With Spring Boot, you can Server-side web applications; a views template engine is responsible for serving dynamic web pages to the user. We will use Thymeleaf in this post and show to integrate it with Tailwind CSS.

Prerequisites

Before following this tutorial, make sure you have the following tools installed on your computer:

Setup the project

We need three dependencies to build the application:

  • Spring Web: Set up the web server, create routes, and render web pages.
  • Thymeleaf: View template engine to build our web page with dynamic content. For this post, the content will be static for brevity.
  • Spring Boot DevTools: Provide the LiveReload when building web pages, so we don't need to restart the application when we make a change.

To set up a Spring Boot project, you can use the online project generator at https://start.spring.io to generate a project with the required dependencies.

You can also use IntelliJ to quickly create a new project; I wrote a post where I guide you on how to do that.

Create a Spring Boot project from IntelliJ
In this post, we will see how to use IntelliJ to create and run a Spring Boot project with Maven dependencies.

Configure Thymeleaf to build web pages

We need to configure Thymeleaf to indicate where the views are located. A view is an HTML file in which you can add custom attributes and expressions interpreted by Thymeleaf to generate a web page dynamically.

Create a package named configs, then create a file named WebConfig.java and finally add the code below:

package com.tericcabrel.webapp.configs;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer
{

}

This configuration enables Thymeleaf with the default parameters; this is why we don't have to add anything inside.

Let's see the project structure:

The highlighted folder called templates is where Thymeleaf will look for HTML files. The folder named static is where to put static content like CSS files, Javascript files, pictures, or raw files.

Build the web page

We need to create a route the user will enter to view the web page in the browser. Technically, we give the instruction to render a specific view using Thymeleaf. Create a package named controllers, then create a file named HomeController.java and finally add the code below:

package com.tericcabrel.webapp.controllers;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping
public class HomeController {

  @GetMapping("/")
  public String indexPage(Model model) {
    model.addAttribute("title", "Spring Boot x Tailwind CSS");

    return "home";
  }

}

Here, we are saying: when a user hits the route "/," render the view "home.html" inside the folder templates.

Line 14 passes a variable named "title" with the value "Spring Boot x Tailwind CSS" from the controller to the view. We do it just to showcase Thymeleaf in action.

But the file doesn't exist yet; the next step is to create it.

Create the file home.html

About the content to put in the file, I found the web page on Tailwind Components about the content to put in the files, a free repository for community components using Tailwind CSS.

This is the link to the page below; please support the creator by sharing it if you like:

Let's put the code for this page in our home.html file:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://wwww.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title th:text="${title}">Home Page</title>
  <script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.x.x/dist/alpine.min.js" defer></script>
</head>
<body>
<div x-data="{ cartOpen: false , isOpen: false }" class="bg-white">
  <header>
    <div class="container mx-auto px-6 py-3">
      <div class="flex items-center justify-between">
        <div class="hidden w-full text-gray-600 md:flex md:items-center">
          <svg class="h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
            <path fill-rule="evenodd" clip-rule="evenodd" d="M16.2721 10.2721C16.2721 12.4813 14.4813 14.2721 12.2721 14.2721C10.063 14.2721 8.27214 12.4813 8.27214 10.2721C8.27214 8.06298 10.063 6.27212 12.2721 6.27212C14.4813 6.27212 16.2721 8.06298 16.2721 10.2721ZM14.2721 10.2721C14.2721 11.3767 13.3767 12.2721 12.2721 12.2721C11.1676 12.2721 10.2721 11.3767 10.2721 10.2721C10.2721 9.16755 11.1676 8.27212 12.2721 8.27212C13.3767 8.27212 14.2721 9.16755 14.2721 10.2721Z" fill="currentColor" /><path fill-rule="evenodd" clip-rule="evenodd" d="M5.79417 16.5183C2.19424 13.0909 2.05438 7.39409 5.48178 3.79417C8.90918 0.194243 14.6059 0.054383 18.2059 3.48178C21.8058 6.90918 21.9457 12.6059 18.5183 16.2059L12.3124 22.7241L5.79417 16.5183ZM17.0698 14.8268L12.243 19.8965L7.17324 15.0698C4.3733 12.404 4.26452 7.97318 6.93028 5.17324C9.59603 2.3733 14.0268 2.26452 16.8268 4.93028C19.6267 7.59603 19.7355 12.0268 17.0698 14.8268Z" fill="currentColor" />
          </svg>
          <span class="mx-1 text-sm">NY</span>
        </div>
        <div class="w-full text-gray-700 md:text-center text-2xl font-semibold">
          Brand
        </div>
        <div class="flex items-center justify-end w-full">
          <button @click="cartOpen = !cartOpen" class="text-gray-600 focus:outline-none mx-4 sm:mx-0">
            <svg class="h-5 w-5" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" stroke="currentColor">
              <path d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path>
            </svg>
          </button>

          <div class="flex sm:hidden">
            <button @click="isOpen = !isOpen" type="button" class="text-gray-600 hover:text-gray-500 focus:outline-none focus:text-gray-500" aria-label="toggle menu">
              <svg viewBox="0 0 24 24" class="h-6 w-6 fill-current">
                <path fill-rule="evenodd" d="M4 5h16a1 1 0 0 1 0 2H4a1 1 0 1 1 0-2zm0 6h16a1 1 0 0 1 0 2H4a1 1 0 0 1 0-2zm0 6h16a1 1 0 0 1 0 2H4a1 1 0 0 1 0-2z"></path>
              </svg>
            </button>
          </div>
        </div>
      </div>
      <nav :class="isOpen ? '' : 'hidden'" class="sm:flex sm:justify-center sm:items-center mt-4">
        <div class="flex flex-col sm:flex-row">
          <a class="mt-3 text-gray-600 hover:underline sm:mx-3 sm:mt-0" href="#">Home</a>
          <a class="mt-3 text-gray-600 hover:underline sm:mx-3 sm:mt-0" href="#">Shop</a>
          <a class="mt-3 text-gray-600 hover:underline sm:mx-3 sm:mt-0" href="#">Categories</a>
          <a class="mt-3 text-gray-600 hover:underline sm:mx-3 sm:mt-0" href="#">Contact</a>
          <a class="mt-3 text-gray-600 hover:underline sm:mx-3 sm:mt-0" href="#">About</a>
        </div>
      </nav>
      <div class="relative mt-6 max-w-lg mx-auto">
            <span class="absolute inset-y-0 left-0 pl-3 flex items-center">
                <svg class="h-5 w-5 text-gray-500" viewBox="0 0 24 24" fill="none">
                    <path d="M21 21L15 15M17 10C17 13.866 13.866 17 10 17C6.13401 17 3 13.866 3 10C3 6.13401 6.13401 3 10 3C13.866 3 17 6.13401 17 10Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
                </svg>
            </span>

        <input class="w-full border rounded-md pl-10 pr-4 py-2 focus:border-blue-500 focus:outline-none focus:shadow-outline" type="text" placeholder="Search">
      </div>
    </div>
  </header>
  <div :class="cartOpen ? 'translate-x-0 ease-out' : 'translate-x-full ease-in'" class="fixed right-0 top-0 max-w-xs w-full h-full px-6 py-4 transition duration-300 transform overflow-y-auto bg-white border-l-2 border-gray-300">
    <div class="flex items-center justify-between">
      <h3 class="text-2xl font-medium text-gray-700">Your cart</h3>
      <button @click="cartOpen = !cartOpen" class="text-gray-600 focus:outline-none">
        <svg class="h-5 w-5" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" stroke="currentColor"><path d="M6 18L18 6M6 6l12 12"></path></svg>
      </button>
    </div>
    <hr class="my-3">
    <div class="flex justify-between mt-6">
      <div class="flex">
        <img class="h-20 w-20 object-cover rounded" src="https://images.unsplash.com/photo-1593642632823-8f785ba67e45?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1189&q=80" alt="">
        <div class="mx-3">
          <h3 class="text-sm text-gray-600">Mac Book Pro</h3>
          <div class="flex items-center mt-2">
            <button class="text-gray-500 focus:outline-none focus:text-gray-600">
              <svg class="h-5 w-5" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" stroke="currentColor"><path d="M12 9v3m0 0v3m0-3h3m-3 0H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
            </button>
            <span class="text-gray-700 mx-2">2</span>
            <button class="text-gray-500 focus:outline-none focus:text-gray-600">
              <svg class="h-5 w-5" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" stroke="currentColor"><path d="M15 12H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
            </button>
          </div>
        </div>
      </div>
      <span class="text-gray-600">20$</span>
    </div>
    <div class="flex justify-between mt-6">
      <div class="flex">
        <img class="h-20 w-20 object-cover rounded" src="https://images.unsplash.com/photo-1593642632823-8f785ba67e45?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1189&q=80" alt="">
        <div class="mx-3">
          <h3 class="text-sm text-gray-600">Mac Book Pro</h3>
          <div class="flex items-center mt-2">
            <button class="text-gray-500 focus:outline-none focus:text-gray-600">
              <svg class="h-5 w-5" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" stroke="currentColor"><path d="M12 9v3m0 0v3m0-3h3m-3 0H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
            </button>
            <span class="text-gray-700 mx-2">2</span>
            <button class="text-gray-500 focus:outline-none focus:text-gray-600">
              <svg class="h-5 w-5" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" stroke="currentColor"><path d="M15 12H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
            </button>
          </div>
        </div>
      </div>
      <span class="text-gray-600">20$</span>
    </div>
    <div class="flex justify-between mt-6">
      <div class="flex">
        <img class="h-20 w-20 object-cover rounded" src="https://images.unsplash.com/photo-1593642632823-8f785ba67e45?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1189&q=80" alt="">
        <div class="mx-3">
          <h3 class="text-sm text-gray-600">Mac Book Pro</h3>
          <div class="flex items-center mt-2">
            <button class="text-gray-500 focus:outline-none focus:text-gray-600">
              <svg class="h-5 w-5" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" stroke="currentColor"><path d="M12 9v3m0 0v3m0-3h3m-3 0H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
            </button>
            <span class="text-gray-700 mx-2">2</span>
            <button class="text-gray-500 focus:outline-none focus:text-gray-600">
              <svg class="h-5 w-5" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" stroke="currentColor"><path d="M15 12H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
            </button>
          </div>
        </div>
      </div>
      <span class="text-gray-600">20$</span>
    </div>
    <div class="mt-8">
      <form class="flex items-center justify-center">
        <input class="form-input w-48" type="text" placeholder="Add promocode">
        <button class="ml-3 flex items-center px-3 py-2 bg-blue-600 text-white text-sm uppercase font-medium rounded hover:bg-blue-500 focus:outline-none focus:bg-blue-500">
          <span>Apply</span>
        </button>
      </form>
    </div>
    <a class="flex items-center justify-center mt-4 px-3 py-2 bg-blue-600 text-white text-sm uppercase font-medium rounded hover:bg-blue-500 focus:outline-none focus:bg-blue-500">
      <span>Chechout</span>
      <svg class="h-5 w-5 mx-2" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" stroke="currentColor"><path d="M17 8l4 4m0 0l-4 4m4-4H3"></path></svg>
    </a>
  </div>
  <main class="my-8">
    <div class="container mx-auto px-6">
      <div class="md:flex md:items-center">
        <div class="w-full h-64 md:w-1/2 lg:h-96">
          <img class="h-full w-full rounded-md object-cover max-w-lg mx-auto" src="https://images.unsplash.com/photo-1578262825743-a4e402caab76?ixlib=rb-1.2.1&auto=format&fit=crop&w=1051&q=80" alt="Nike Air">
        </div>
        <div class="w-full max-w-lg mx-auto mt-5 md:ml-8 md:mt-0 md:w-1/2">
          <h3 class="text-gray-700 uppercase text-lg">Nike Air</h3>
          <span class="text-gray-500 mt-3">$125</span>
          <hr class="my-3">
          <div class="mt-2">
            <label class="text-gray-700 text-sm">Count:</label>
            <div class="flex items-center mt-1">
              <button class="text-gray-500 focus:outline-none focus:text-gray-600">
                <svg class="h-5 w-5" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" stroke="currentColor"><path d="M12 9v3m0 0v3m0-3h3m-3 0H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
              </button>
              <span class="text-gray-700 text-lg mx-2">20</span>
              <button class="text-gray-500 focus:outline-none focus:text-gray-600">
                <svg class="h-5 w-5" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" stroke="currentColor"><path d="M15 12H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
              </button>
            </div>
          </div>
          <div class="mt-3">
            <label class="text-gray-700 text-sm">Color:</label>
            <div class="flex items-center mt-1">
              <button class="h-5 w-5 rounded-full bg-blue-600 border-2 border-blue-200 mr-2 focus:outline-none"></button>
              <button class="h-5 w-5 rounded-full bg-teal-600 mr-2 focus:outline-none"></button>
              <button class="h-5 w-5 rounded-full bg-pink-600 mr-2 focus:outline-none"></button>
            </div>
          </div>
          <div class="flex items-center mt-6">
            <button class="px-8 py-2 bg-indigo-600 text-white text-sm font-medium rounded hover:bg-indigo-500 focus:outline-none focus:bg-indigo-500">Order Now</button>
            <button class="mx-2 text-gray-600 border rounded-md p-2 hover:bg-gray-200 focus:outline-none">
              <svg class="h-5 w-5" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" stroke="currentColor"><path d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path></svg>
            </button>
          </div>
        </div>
      </div>
      <div class="mt-16">
        <h3 class="text-gray-600 text-2xl font-medium">More Products</h3>
        <div class="grid gap-6 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 mt-6">
          <div class="w-full max-w-sm mx-auto rounded-md shadow-md overflow-hidden">
            <div class="flex items-end justify-end h-56 w-full bg-cover" style="background-image: url('https://images.unsplash.com/photo-1563170351-be82bc888aa4?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=376&q=80')">
              <button class="p-2 rounded-full bg-blue-600 text-white mx-5 -mb-4 hover:bg-blue-500 focus:outline-none focus:bg-blue-500">
                <svg class="h-5 w-5" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" stroke="currentColor"><path d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path></svg>
              </button>
            </div>
            <div class="px-5 py-3">
              <h3 class="text-gray-700 uppercase">Chanel</h3>
              <span class="text-gray-500 mt-2">$12</span>
            </div>
          </div>
          <div class="w-full max-w-sm mx-auto rounded-md shadow-md overflow-hidden">
            <div class="flex items-end justify-end h-56 w-full bg-cover" style="background-image: url('https://images.unsplash.com/photo-1544441893-675973e31985?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1500&q=80')">
              <button class="p-2 rounded-full bg-blue-600 text-white mx-5 -mb-4 hover:bg-blue-500 focus:outline-none focus:bg-blue-500">
                <svg class="h-5 w-5" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" stroke="currentColor"><path d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path></svg>
              </button>
            </div>
            <div class="px-5 py-3">
              <h3 class="text-gray-700 uppercase">Man Mix</h3>
              <span class="text-gray-500 mt-2">$12</span>
            </div>
          </div>
          <div class="w-full max-w-sm mx-auto rounded-md shadow-md overflow-hidden">
            <div class="flex items-end justify-end h-56 w-full bg-cover" style="background-image: url('https://images.unsplash.com/photo-1532667449560-72a95c8d381b?ixlib=rb-1.2.1&auto=format&fit=crop&w=750&q=80')">
              <button class="p-2 rounded-full bg-blue-600 text-white mx-5 -mb-4 hover:bg-blue-500 focus:outline-none focus:bg-blue-500">
                <svg class="h-5 w-5" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" stroke="currentColor"><path d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path></svg>
              </button>
            </div>
            <div class="px-5 py-3">
              <h3 class="text-gray-700 uppercase">Classic watch</h3>
              <span class="text-gray-500 mt-2">$12</span>
            </div>
          </div>
          <div class="w-full max-w-sm mx-auto rounded-md shadow-md overflow-hidden">
            <div class="flex items-end justify-end h-56 w-full bg-cover" style="background-image: url('https://images.unsplash.com/photo-1590664863685-a99ef05e9f61?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=345&q=80')">
              <button class="p-2 rounded-full bg-blue-600 text-white mx-5 -mb-4 hover:bg-blue-500 focus:outline-none focus:bg-blue-500">
                <svg class="h-5 w-5" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" stroke="currentColor"><path d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path></svg>
              </button>
            </div>
            <div class="px-5 py-3">
              <h3 class="text-gray-700 uppercase">woman mix</h3>
              <span class="text-gray-500 mt-2">$12</span>
            </div>
          </div>
        </div>
      </div>
    </div>
  </main>

  <footer class="bg-gray-200">
    <div class="container mx-auto px-6 py-3 flex justify-between items-center">
      <a href="#" class="text-xl font-bold text-gray-500 hover:text-gray-400">Brand</a>
      <p class="py-2 text-gray-500 sm:py-0">All rights reserved</p>
    </div>
  </footer>
</div>
</body>
</html>

In line 5, we indicate that we use his value if the variable title is not null for the title page; otherwise, we fall back to "Home Page."

Run the project and navigate to http://localhost:8080.

We get this ugly page because the style generated by Tailwind CSS is missing.

Install and Set up Tailwind CSS

We will install Tailwind CLI from the NPM; before that, let's init a new Node.js project by running this command at the project root directory :

npm init -y

Install the node package:

npm i -D tailwindcss

Generate the Tailwind CSS configuration file with this command:

npx tailwindcss init

A file named "tailwind.config.js" will be generated; open it and replace the content with this one:

module.exports = {
  content: ["./src/main/resources/templates/**/*.{html,js}"],
  theme: {
    extend: {},
  },
  plugins: [],
}

We indicate to Tailwind CLI where to look for files that contain the Tailwind CSS classes.

At the root project directory, create a folder named "styles," then add a file named "input.css," and finally add the code below inside:

@tailwind base;
@tailwind components;
@tailwind utilities;

This is what the project structure looks like:

Generate the CSS from Tailwind classes

We need to generate the required CSS using the CLI; the command requires two options:

  • The input: The CSS entry file to use; this file usually contains the Tailwind CSS base CSS, the global CSS of the application, and the CSS of the external packages used in the project.
  • The output: The path where to generate the CSS output.

Here is the command to run:

npx tailwindcss -i styles/input.css -o ./src/main/resources/static/css/main.css

The output path is the static folder, and the name is "main.css."

After running the command, you will see the file at the expected location. Add the code below in the head's tag of the home.html.

<link rel="stylesheet" th:href="@{/css/main.css}" />

Thymeleaf will resolve the path, so the CSS is served as expected.

Rerun the application and watch the result:

The web page now looks great ?

Watching Tailwind CSS classes

Every time you add or remove a class in your HMTL file, you need to rebuild your CSS file; this is not a good developer experience. Fortunately, the Tailwind CSS CLI provides an option to watch files and rebuild the CSS on change. Just add the option "--watch" to the previous command.

npx tailwindcss -i styles/input.css -o ./src/main/resources/static/css/main.css --watch

Wrap up

We saw how to build a project Spring Boot project we Thymeleaf and Tailwind CSS. If you want to go further with these tools, find their respective documentation below:

You can find the code source on the GitHub repository.

Follow me on Twitter or subscribe to my newsletter to avoid missing the upcoming posts and the tips and tricks I occasionally share.