.cursor/skills/thymeleaf-patterns/SKILL.md
Thymeleaf + Spring Boot UI conventions, fragment patterns, form handling, and accessibility standards for this project.
npx skillsauth add BhumitThakkar/cursor-kit thymeleaf-patternsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
src/main/resources/templates/
├── fragments/
│ ├── layout.html # Base layout — header, nav, footer
│ ├── header.html # Site header fragment
│ ├── nav.html # Navigation fragment
│ └── alerts.html # Success/error flash message fragment
├── events/
│ ├── list.html
│ ├── detail.html
│ └── form.html
└── bookings/
├── form.html
└── confirmation.html
<!-- fragments/layout.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title th:text="${pageTitle} + ' | Temple'">Temple</title>
<link rel="stylesheet" th:href="@{/css/main.css}">
</head>
<body>
<header th:replace="~{fragments/header :: header}"></header>
<nav th:replace="~{fragments/nav :: nav}"></nav>
<main id="main-content" role="main">
<div th:replace="~{fragments/alerts :: alerts}"></div>
<div th:replace="${content}"></div>
</main>
<footer th:replace="~{fragments/footer :: footer}"></footer>
</body>
</html>
<!-- events/list.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
th:replace="~{fragments/layout :: layout(content=~{::main-content})}">
<body>
<th:block th:fragment="main-content">
<h1>Upcoming Events</h1>
<!-- page content here -->
</th:block>
</body>
</html>
<form th:action="@{/bookings}" th:object="${bookingRequest}" method="post">
<!-- Name field with label and error -->
<div class="form-group">
<label for="name">Full Name</label>
<input type="text"
id="name"
th:field="*{name}"
class="form-control"
th:classappend="${#fields.hasErrors('name')} ? 'is-invalid'"
placeholder="Enter your full name"
required>
<span class="invalid-feedback"
th:if="${#fields.hasErrors('name')}"
th:errors="*{name}">Name error</span>
</div>
<!-- Select field -->
<div class="form-group">
<label for="hallType">Hall Type</label>
<select id="hallType" th:field="*{hallType}" class="form-control">
<option value="">-- Select --</option>
<option th:each="type : ${hallTypes}"
th:value="${type}"
th:text="${type.displayName}">Hall</option>
</select>
</div>
<!-- Date field -->
<div class="form-group">
<label for="eventDate">Event Date</label>
<input type="date"
id="eventDate"
th:field="*{eventDate}"
class="form-control"
required>
</div>
<button type="submit" class="btn btn-primary">Submit Booking</button>
</form>
<!-- fragments/alerts.html -->
<th:block th:fragment="alerts">
<div class="alert alert-success"
th:if="${successMessage}"
role="alert"
th:text="${successMessage}">
</div>
<div class="alert alert-danger"
th:if="${errorMessage}"
role="alert"
th:text="${errorMessage}">
</div>
</th:block>
// In controller — redirect with flash message
redirectAttributes.addFlashAttribute("successMessage", "Booking confirmed!");
return "redirect:/bookings";
<!-- List with empty state -->
<th:block th:if="${not #lists.isEmpty(events)}">
<ul class="event-list">
<li th:each="event : ${events}" class="event-item">
<a th:href="@{/events/{id}(id=${event.id})}"
th:text="${event.title}">Event Title</a>
<span th:text="${#temporals.format(event.date, 'dd MMM yyyy')}">Date</span>
</li>
</ul>
</th:block>
<div class="empty-state" th:if="${#lists.isEmpty(events)}">
<p>No events scheduled. Check back soon.</p>
</div>
<!-- Show content only to authenticated users -->
<div th:if="${#authorization.expression('isAuthenticated()')}">
<a th:href="@{/admin}">Admin Panel</a>
</div>
<!-- Show content only to admins -->
<div th:if="${#authorization.expression('hasRole(''ADMIN'')')}">
<a th:href="@{/admin/events/new}">Add Event</a>
</div>
<!-- Always use @{} — never hardcode paths -->
<a th:href="@{/events}">Events</a>
<a th:href="@{/events/{id}(id=${event.id})}">View Event</a>
<a th:href="@{/events/{id}/edit(id=${event.id})}">Edit</a>
<img th:src="@{/images/{name}(name=${event.imageFile})}" th:alt="${event.title}">
<link rel="stylesheet" th:href="@{/css/main.css}">
<input> has a <label> with matching for attribute or wraps the input<img> has meaningful alt textaria-describedby or th:errors<h1> — heading hierarchy is logical<main role="main"> wraps the primary contentmessages.properties)messages.properties: #{msg.key}; provide messages_en.properties, messages_hi.properties, etc. as needed.pom.xml / Gradle.th:utext safetyth:text. Use th:utext only for sanitized or fully trusted HTML; pipe user content through OWASP Java HTML Sanitizer or similar.unsafe-inline in production.onclick attributes; prefer external JS modules.th:action on mutating formsth:utext reviewed for trust boundarydevelopment
Read-only checklist for Jakarta Bean Validation on Spring API models (DTOs, request bodies). Produces a markdown report with PASS/FAIL; does not modify source files.
development
QA-oriented playbook for verifying test coverage on new or changed code (JaCoCo / Maven Gradle) against project quality gate expectations. Read-only analysis instructions unless user authorizes runs.
testing
Maintain AGENTS.md roster and disabled.txt without deleting agent files without explicit approval.
development
Spring Boot coding standards, patterns, and conventions for this project. Uses SLF4J API with Log4j2 as the logging implementation. Loaded dynamically when implementing Java/Spring Boot code.