Skip to content

Checkbox

Checkbox
Thymeleaf source
<sl:label><sl:checkbox th:field="*{terms}"/>Accept the terms and conditions</sl:label>
Rendered HTML
<label class="label">
<span class="checkbox-wrapper">
<input type="checkbox" class="checkbox" name="terms">
<svg xmlns="http://www.w3.org/2000/svg" class="icon checkbox-indicator" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="M20 6 9 17l-5-5"></path>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" class="icon checkbox-indeterminate-indicator" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="M5 12h14"></path>
</svg>
</span>
Accept the terms and conditions
</label>

th:field on a boolean field sets checked and adds the hidden _terms input Spring uses to see an unchecked box. For a collection field, give each checkbox a value (or th:value), as with a plain checkbox.

<form th:object="${signup}" th:action="@{/signup}" method="post">
<sl:label><sl:checkbox th:field="*{terms}"/>Accept the terms and conditions</sl:label>
</form>

Put the checkbox inside its label: clicking the text toggles it, and it needs no id. Spring numbers the id of a checkbox (terms1), so a label with for="terms" would miss it. For a label beside the checkbox, give the checkbox an explicit id, which wins over the numbered one:

With a separate labelGive the checkbox an explicit id for the label's for; th:field would number it.
Thymeleaf source
<sl:checkbox id="checkbox-notify" name="notify"/>
<sl:label for="checkbox-notify">Notify me by email</sl:label>
Rendered HTML
<span class="checkbox-wrapper">
<input type="checkbox" class="checkbox" id="checkbox-notify" name="notify">
<svg xmlns="http://www.w3.org/2000/svg" class="icon checkbox-indicator" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="M20 6 9 17l-5-5"></path>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" class="icon checkbox-indeterminate-indicator" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="M5 12h14"></path>
</svg>
</span>
<label class="label" for="checkbox-notify">Notify me by email</label>

In a field with th:field, the checkbox gets the field’s name as its id (terms), and the field’s label points at it. The field also shows the error message.

Checked
Thymeleaf source
<sl:label><sl:checkbox name="newsletter" checked/>Send me the newsletter</sl:label>
Rendered HTML
<label class="label">
<span class="checkbox-wrapper">
<input type="checkbox" class="checkbox" name="newsletter" checked>
<svg xmlns="http://www.w3.org/2000/svg" class="icon checkbox-indicator" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="M20 6 9 17l-5-5"></path>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" class="icon checkbox-indeterminate-indicator" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="M5 12h14"></path>
</svg>
</span>
Send me the newsletter
</label>

Neither checked nor unchecked: some of a group is selected. HTML has no attribute for it, only the DOM property indeterminate. indeterminate on <sl:checkbox> renders data-indeterminate="true", from which Shadleaf’s script sets the property when the page loads or htmx swaps the checkbox in; the box then shows a minus, and a screen reader says “mixed”. A click clears it and checks the box, as with any checkbox. A script of yours can set the property on any checkbox at any time; the minus follows it (the CSS reads :indeterminate). The script is not an Alpine component, so it also runs with shadleaf.assets.alpine=external; without JavaScript the checkbox is unchecked.

IndeterminateSome of a group selected. A click clears the state, as on any checkbox; a script can set it again.
Thymeleaf source
<sl:label><sl:checkbox name="toppings-all" indeterminate/>All toppings</sl:label>
Rendered HTML
<label class="label">
<span class="checkbox-wrapper">
<input type="checkbox" class="checkbox" data-indeterminate="true" name="toppings-all">
<svg xmlns="http://www.w3.org/2000/svg" class="icon checkbox-indicator" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="M20 6 9 17l-5-5"></path>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" class="icon checkbox-indeterminate-indicator" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="M5 12h14"></path>
</svg>
</span>
All toppings
</label>

Select-all in a table is the common case, and sets the state itself.

A field with errors marks the checkbox aria-invalid="true", as with the input.

InvalidWith th:field, a field that has errors marks the checkbox aria-invalid by itself.
Thymeleaf source
<sl:label><sl:checkbox th:field="*{terms}"/>Accept the terms and conditions</sl:label>
Rendered HTML
<label class="label">
<span class="checkbox-wrapper">
<input type="checkbox" class="checkbox" name="terms" aria-invalid="true">
<svg xmlns="http://www.w3.org/2000/svg" class="icon checkbox-indicator" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="M20 6 9 17l-5-5"></path>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" class="icon checkbox-indeterminate-indicator" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="M5 12h14"></path>
</svg>
</span>
Accept the terms and conditions
</label>

A disabled checkbox fades with its check mark, and a label fades with the disabled checkbox it holds, or sits next to. In Windows’ high-contrast mode the browser draws its own disabled checkbox. HTML has no read-only checkbox: readonly does nothing on one.

Disabled
Thymeleaf source
<sl:label><sl:checkbox name="archived" checked disabled/>Archived</sl:label>
<sl:label><sl:checkbox name="deleted" disabled/>Deleted</sl:label>
Rendered HTML
<label class="label">
<span class="checkbox-wrapper">
<input type="checkbox" class="checkbox" name="archived" checked disabled>
<svg xmlns="http://www.w3.org/2000/svg" class="icon checkbox-indicator" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="M20 6 9 17l-5-5"></path>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" class="icon checkbox-indeterminate-indicator" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="M5 12h14"></path>
</svg>
</span>
Archived
</label>
<label class="label">
<span class="checkbox-wrapper">
<input type="checkbox" class="checkbox" name="deleted" disabled>
<svg xmlns="http://www.w3.org/2000/svg" class="icon checkbox-indicator" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="M20 6 9 17l-5-5"></path>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" class="icon checkbox-indeterminate-indicator" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="M5 12h14"></path>
</svg>
</span>
Deleted
</label>

The check mark and the minus are icons inside a wrapper around the <input>, not CSS background images, so the checkbox renders under a strict img-src policy. class goes to that wrapper; every other attribute to the <input>. In Windows’ high-contrast mode the browser draws its own checkbox, with its own dash when it is indeterminate.

AttributeValuesDefaultDescription
indeterminateboolean—Start in the indeterminate state. HTML has no attribute for it, only the DOM property indeterminate: this renders data-indeterminate, from which Shadleaf's script sets the property when the page loads (or htmx swaps the checkbox in). A click clears it, as it does on every checkbox. A script can set the property on any checkbox; the minus follows it.

Every prop can also be set from an expression: th:indeterminate="${...}". Any other attribute passes through to the rendered element, and class is appended.