Skip to content

Date Picker

Date pickerA date in a field. The button shows "Pick a date" until a date is chosen; a pick closes the popover.
Thymeleaf source
<sl:field th:field="*{pickupDate}">
<sl:field-label>Pickup date</sl:field-label>
<sl:date-picker/>
</sl:field>
Rendered HTML
<div class="field">
<label class="label field-label" for="pickupDate">Pickup date</label>
<div class="date-picker" x-data="slDatePicker" data-placeholder="Pick a date" data-range-format="{0} – {1}">
<button class="button popover-trigger date-picker-trigger" type="button" data-variant="outline" popovertarget="sl-date-picker-qwktiplr" aria-haspopup="dialog" data-empty="true">
<span class="button-icon" data-icon="inline-start">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="M8 2v3"></path>
<path d="M16 2v3"></path>
<rect x="3" y="3" width="18" height="18" rx="2"></rect>
<path d="M3 9h18"></path>
</svg>
</span>
<span class="date-picker-value">Pick a date</span>
</button>
<div class="popover-content date-picker-content" id="sl-date-picker-qwktiplr" role="dialog" popover tabindex="-1" x-data="slPopover" data-align="start" aria-label="Choose a date">
<div class="calendar" x-data="slCalendar" role="group" data-locale="en" data-week-starts-on="0" data-first-week-min-days="1">
<input type="date" class="input calendar-input" id="pickupDate" name="pickupDate" value="">
<template class="calendar-template">
<div class="calendar-nav">
<button type="button" class="calendar-nav-button calendar-previous" aria-label="Go to the previous month">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="m15 18-6-6 6-6"></path>
</svg>
</button>
<button type="button" class="calendar-nav-button calendar-next" aria-label="Go to the next month">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="m9 18 6-6-6-6"></path>
</svg>
</button>
</div>
<label class="calendar-dropdown" data-unit="month">
<span class="calendar-dropdown-label">
<span></span>
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="m6 9 6 6 6-6"></path>
</svg>
</span>
<select class="calendar-select" aria-label="Month"></select>
</label>
<label class="calendar-dropdown" data-unit="year">
<span class="calendar-dropdown-label">
<span></span>
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="m6 9 6 6 6-6"></path>
</svg>
</span>
<select class="calendar-select" aria-label="Year"></select>
</label>
<span class="calendar-week-number-label">Week</span>
</template>
</div>
</div>
</div>
</div>

A date picker is a button in a popover with a calendar: the button shows the chosen date, and a pick closes the popover. <sl:date-picker> is that composition as one component, with the glue in the slDatePicker Alpine component.

The value lives in the calendar’s date inputs, so th:field binds a date picker as it binds a calendar, and the form submits ISO dates (2026-10-23). The LocalDate fields note applies here too.

<form th:object="${booking}" th:action="@{/bookings}" method="post">
<sl:field th:field="*{pickupDate}">
<sl:field-label>Pickup date</sl:field-label>
<sl:date-picker/>
<sl:field-error/>
</sl:field>
</form>

Every attribute that is not a prop goes to the calendar inside. class is the exception: it stays on the date picker. So the calendar’s props work on the date picker (caption-layout, number-of-months, disabled-dates, …), and so do its input’s attributes (th:field, name, required, min, max, disabled, hx-*).

The button shows the chosen date, or placeholder (“Pick a date”) while there is none.

  • On the server, the date is formatted in the page’s locale (Spring’s LocaleResolver). That is what shows before JavaScript runs.
  • In the browser, slDatePicker formats it again with Intl, for the same locale, when it starts and after every change. Java and Intl both use the CLDR’s patterns, so the two texts agree.

format picks the style, as Intl.DateTimeFormat’s dateStyle and Java’s FormatStyle:

format en-US nl-BE
short 10/23/26 23/10/2026
medium Oct 23, 2026 23 okt 2026
long (default) October 23, 2026 23 oktober 2026
full Friday, October 23, 2026 vrijdag 23 oktober 2026
Formatformat is Intl's dateStyle (Java's FormatStyle on the server); long is the default.
Thymeleaf source
<sl:field-group>
<sl:field th:field="*{reminderShort}">
<sl:field-label>Short</sl:field-label>
<sl:date-picker format="short"/>
</sl:field>
<sl:field th:field="*{reminderFull}">
<sl:field-label>Full</sl:field-label>
<sl:date-picker format="full"/>
</sl:field>
</sl:field-group>
Rendered HTML
<div class="field-group">
<div class="field">
<label class="label field-label" for="reminderShort">Short</label>
<div class="date-picker" x-data="slDatePicker" data-date-style="short" data-placeholder="Pick a date" data-range-format="{0} – {1}">
<button class="button popover-trigger date-picker-trigger" type="button" data-variant="outline" popovertarget="sl-date-picker-rnq275sm" aria-haspopup="dialog">
<span class="button-icon" data-icon="inline-start">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="M8 2v3"></path>
<path d="M16 2v3"></path>
<rect x="3" y="3" width="18" height="18" rx="2"></rect>
<path d="M3 9h18"></path>
</svg>
</span>
<span class="date-picker-value">10/23/26</span>
</button>
<div class="popover-content date-picker-content" id="sl-date-picker-rnq275sm" role="dialog" popover tabindex="-1" x-data="slPopover" data-align="start" aria-label="Choose a date">
<div class="calendar" x-data="slCalendar" role="group" data-locale="en" data-week-starts-on="0" data-first-week-min-days="1">
<input type="date" class="input calendar-input" id="reminderShort" name="reminderShort" value="2026-10-23">
<template class="calendar-template">
<div class="calendar-nav">
<button type="button" class="calendar-nav-button calendar-previous" aria-label="Go to the previous month">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="m15 18-6-6 6-6"></path>
</svg>
</button>
<button type="button" class="calendar-nav-button calendar-next" aria-label="Go to the next month">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="m9 18 6-6-6-6"></path>
</svg>
</button>
</div>
<label class="calendar-dropdown" data-unit="month">
<span class="calendar-dropdown-label">
<span></span>
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="m6 9 6 6 6-6"></path>
</svg>
</span>
<select class="calendar-select" aria-label="Month"></select>
</label>
<label class="calendar-dropdown" data-unit="year">
<span class="calendar-dropdown-label">
<span></span>
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="m6 9 6 6 6-6"></path>
</svg>
</span>
<select class="calendar-select" aria-label="Year"></select>
</label>
<span class="calendar-week-number-label">Week</span>
</template>
</div>
</div>
</div>
</div>
<div class="field">
<label class="label field-label" for="reminderFull">Full</label>
<div class="date-picker" x-data="slDatePicker" data-date-style="full" data-placeholder="Pick a date" data-range-format="{0} – {1}">
<button class="button popover-trigger date-picker-trigger" type="button" data-variant="outline" popovertarget="sl-date-picker-obto1vpo" aria-haspopup="dialog">
<span class="button-icon" data-icon="inline-start">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="M8 2v3"></path>
<path d="M16 2v3"></path>
<rect x="3" y="3" width="18" height="18" rx="2"></rect>
<path d="M3 9h18"></path>
</svg>
</span>
<span class="date-picker-value">Friday, October 23, 2026</span>
</button>
<div class="popover-content date-picker-content" id="sl-date-picker-obto1vpo" role="dialog" popover tabindex="-1" x-data="slPopover" data-align="start" aria-label="Choose a date">
<div class="calendar" x-data="slCalendar" role="group" data-locale="en" data-week-starts-on="0" data-first-week-min-days="1">
<input type="date" class="input calendar-input" id="reminderFull" name="reminderFull" value="2026-10-23">
<template class="calendar-template">
<div class="calendar-nav">
<button type="button" class="calendar-nav-button calendar-previous" aria-label="Go to the previous month">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="m15 18-6-6 6-6"></path>
</svg>
</button>
<button type="button" class="calendar-nav-button calendar-next" aria-label="Go to the next month">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="m9 18 6-6-6-6"></path>
</svg>
</button>
</div>
<label class="calendar-dropdown" data-unit="month">
<span class="calendar-dropdown-label">
<span></span>
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="m6 9 6 6 6-6"></path>
</svg>
</span>
<select class="calendar-select" aria-label="Month"></select>
</label>
<label class="calendar-dropdown" data-unit="year">
<span class="calendar-dropdown-label">
<span></span>
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="m6 9 6 6 6-6"></path>
</svg>
</span>
<select class="calendar-select" aria-label="Year"></select>
</label>
<span class="calendar-week-number-label">Week</span>
</template>
</div>
</div>
</div>
</div>
</div>
With a dateThe server formats the bound date in the page's locale; the browser formats it the same way after a pick.
Thymeleaf source
<sl:field th:field="*{appointmentDate}">
<sl:field-label>Appointment</sl:field-label>
<sl:date-picker/>
</sl:field>
Rendered HTML
<div class="field">
<label class="label field-label" for="appointmentDate">Appointment</label>
<div class="date-picker" x-data="slDatePicker" data-placeholder="Pick a date" data-range-format="{0} – {1}">
<button class="button popover-trigger date-picker-trigger" type="button" data-variant="outline" popovertarget="sl-date-picker-cfuq67ir" aria-haspopup="dialog">
<span class="button-icon" data-icon="inline-start">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="M8 2v3"></path>
<path d="M16 2v3"></path>
<rect x="3" y="3" width="18" height="18" rx="2"></rect>
<path d="M3 9h18"></path>
</svg>
</span>
<span class="date-picker-value">October 23, 2026</span>
</button>
<div class="popover-content date-picker-content" id="sl-date-picker-cfuq67ir" role="dialog" popover tabindex="-1" x-data="slPopover" data-align="start" aria-label="Choose a date">
<div class="calendar" x-data="slCalendar" role="group" data-locale="en" data-week-starts-on="0" data-first-week-min-days="1">
<input type="date" class="input calendar-input" id="appointmentDate" name="appointmentDate" value="2026-10-23">
<template class="calendar-template">
<div class="calendar-nav">
<button type="button" class="calendar-nav-button calendar-previous" aria-label="Go to the previous month">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="m15 18-6-6 6-6"></path>
</svg>
</button>
<button type="button" class="calendar-nav-button calendar-next" aria-label="Go to the next month">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="m9 18 6-6-6-6"></path>
</svg>
</button>
</div>
<label class="calendar-dropdown" data-unit="month">
<span class="calendar-dropdown-label">
<span></span>
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="m6 9 6 6 6-6"></path>
</svg>
</span>
<select class="calendar-select" aria-label="Month"></select>
</label>
<label class="calendar-dropdown" data-unit="year">
<span class="calendar-dropdown-label">
<span></span>
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="m6 9 6 6 6-6"></path>
</svg>
</span>
<select class="calendar-select" aria-label="Year"></select>
</label>
<span class="calendar-week-number-label">Week</span>
</template>
</div>
</div>
</div>
</div>

For a range, give the date picker two date inputs as content, the start and then the end, and an aria-label that names the range. The popover closes after the second pick. The button shows both ends: Oct 12, 2026 – Oct 21, 2026 with format="medium" (the separator is the message sl.date-picker.range, see Translating).

RangeTwo date inputs, the start and the end, make a range; the popover closes after the second pick.
Thymeleaf source
<sl:date-picker aria-label="Trip" format="medium" number-of-months="2">
<sl:input type="date" th:field="*{tripStart}" aria-label="Departure"/>
<sl:input type="date" th:field="*{tripEnd}" aria-label="Return"/>
</sl:date-picker>
Rendered HTML
<div class="date-picker" x-data="slDatePicker" data-date-style="medium" data-placeholder="Pick a date" data-range-format="{0} – {1}">
<button class="button popover-trigger date-picker-trigger" type="button" data-variant="outline" popovertarget="sl-date-picker-i3m9oq2t" aria-haspopup="dialog">
<span class="button-icon" data-icon="inline-start">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="M8 2v3"></path>
<path d="M16 2v3"></path>
<rect x="3" y="3" width="18" height="18" rx="2"></rect>
<path d="M3 9h18"></path>
</svg>
</span>
<span class="date-picker-value">Oct 12, 2026 – Oct 21, 2026</span>
</button>
<div class="popover-content date-picker-content" id="sl-date-picker-i3m9oq2t" role="dialog" popover tabindex="-1" x-data="slPopover" data-align="start" aria-label="Choose a date">
<div class="calendar" x-data="slCalendar" role="group" data-number-of-months="2" data-locale="en" data-week-starts-on="0" data-first-week-min-days="1" aria-label="Trip">
<input class="input" type="date" aria-label="Departure" id="tripStart" name="tripStart" value="2026-10-12">
<input class="input" type="date" aria-label="Return" id="tripEnd" name="tripEnd" value="2026-10-21">
<template class="calendar-template">
<div class="calendar-nav">
<button type="button" class="calendar-nav-button calendar-previous" aria-label="Go to the previous month">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="m15 18-6-6 6-6"></path>
</svg>
</button>
<button type="button" class="calendar-nav-button calendar-next" aria-label="Go to the next month">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="m9 18 6-6-6-6"></path>
</svg>
</button>
</div>
<label class="calendar-dropdown" data-unit="month">
<span class="calendar-dropdown-label">
<span></span>
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="m6 9 6 6 6-6"></path>
</svg>
</span>
<select class="calendar-select" aria-label="Month"></select>
</label>
<label class="calendar-dropdown" data-unit="year">
<span class="calendar-dropdown-label">
<span></span>
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="m6 9 6 6 6-6"></path>
</svg>
</span>
<select class="calendar-select" aria-label="Year"></select>
</label>
<span class="calendar-week-number-label">Week</span>
</template>
</div>
</div>
</div>

The calendar’s month and year selects (caption-layout="dropdown") help with dates far from today.

Date of birthThe calendar's props pass through, such as caption-layout="dropdown" for a date far from today.
Thymeleaf source
<sl:field th:field="*{dateOfBirth}">
<sl:field-label>Date of birth</sl:field-label>
<sl:date-picker format="medium" placeholder="Select date" caption-layout="dropdown" start-month="1926-01"
end-month="2026-12" max="2026-10-23"/>
</sl:field>
Rendered HTML
<div class="field">
<label class="label field-label" for="dateOfBirth">Date of birth</label>
<div class="date-picker" x-data="slDatePicker" data-date-style="medium" data-placeholder="Select date" data-range-format="{0} – {1}">
<button class="button popover-trigger date-picker-trigger" type="button" data-variant="outline" popovertarget="sl-date-picker-10b5ihtx" aria-haspopup="dialog" data-empty="true">
<span class="button-icon" data-icon="inline-start">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="M8 2v3"></path>
<path d="M16 2v3"></path>
<rect x="3" y="3" width="18" height="18" rx="2"></rect>
<path d="M3 9h18"></path>
</svg>
</span>
<span class="date-picker-value">Select date</span>
</button>
<div class="popover-content date-picker-content" id="sl-date-picker-10b5ihtx" role="dialog" popover tabindex="-1" x-data="slPopover" data-align="start" aria-label="Choose a date">
<div class="calendar" x-data="slCalendar" role="group" data-caption="dropdown" data-locale="en" data-week-starts-on="0" data-first-week-min-days="1" data-start-month="1926-01" data-end-month="2026-12">
<input type="date" class="input calendar-input" max="2026-10-23" id="dateOfBirth" name="dateOfBirth" value="">
<template class="calendar-template">
<div class="calendar-nav">
<button type="button" class="calendar-nav-button calendar-previous" aria-label="Go to the previous month">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="m15 18-6-6 6-6"></path>
</svg>
</button>
<button type="button" class="calendar-nav-button calendar-next" aria-label="Go to the next month">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="m9 18 6-6-6-6"></path>
</svg>
</button>
</div>
<label class="calendar-dropdown" data-unit="month">
<span class="calendar-dropdown-label">
<span></span>
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="m6 9 6 6 6-6"></path>
</svg>
</span>
<select class="calendar-select" aria-label="Month"></select>
</label>
<label class="calendar-dropdown" data-unit="year">
<span class="calendar-dropdown-label">
<span></span>
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="m6 9 6 6 6-6"></path>
</svg>
</span>
<select class="calendar-select" aria-label="Year"></select>
</label>
<span class="calendar-week-number-label">Week</span>
</template>
</div>
</div>
</div>
</div>
  • The label. In a field, the label points at the date input inside the popover, as for a calendar. The button takes the label’s text and the date it shows as its name (“Pickup date, October 23, 2026”), and a click on the label focuses the button.
  • Errors. A field with errors marks the input and the button aria-invalid="true", and the field’s error describes both.
  • Validation. The browser can only point at a control it can show. When a required date is empty on submit, the popover opens, so the browser’s message points at the calendar.
  • Events. A pick fires input and change on the input, as on a calendar.
  • A value set by a script shows on the button once the script fires change on the input.
  • Form reset puts the button’s text back too.
With errorsThe field's error marks the button aria-invalid and describes it. A required empty date opens the popover on submit.
Thymeleaf source
<sl:field th:field="*{returnDate}">
<sl:field-label>Return date</sl:field-label>
<sl:date-picker required default-month="2026-10"/>
<sl:field-error/>
</sl:field>
Rendered HTML
<div class="field" data-invalid="true">
<label class="label field-label" for="returnDate">Return date</label>
<div class="date-picker" x-data="slDatePicker" data-placeholder="Pick a date" data-range-format="{0} – {1}">
<button class="button popover-trigger date-picker-trigger" type="button" data-variant="outline" popovertarget="sl-date-picker-dx28o4p8" aria-haspopup="dialog" data-empty="true">
<span class="button-icon" data-icon="inline-start">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="M8 2v3"></path>
<path d="M16 2v3"></path>
<rect x="3" y="3" width="18" height="18" rx="2"></rect>
<path d="M3 9h18"></path>
</svg>
</span>
<span class="date-picker-value">Pick a date</span>
</button>
<div class="popover-content date-picker-content" id="sl-date-picker-dx28o4p8" role="dialog" popover tabindex="-1" x-data="slPopover" data-align="start" aria-label="Choose a date">
<div class="calendar" x-data="slCalendar" role="group" data-locale="en" data-week-starts-on="0" data-first-week-min-days="1" data-default-month="2026-10">
<input type="date" class="input calendar-input" aria-invalid="true" required id="returnDate" aria-describedby="returnDate-error" name="returnDate" value="">
<template class="calendar-template">
<div class="calendar-nav">
<button type="button" class="calendar-nav-button calendar-previous" aria-label="Go to the previous month">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="m15 18-6-6 6-6"></path>
</svg>
</button>
<button type="button" class="calendar-nav-button calendar-next" aria-label="Go to the next month">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="m9 18 6-6-6-6"></path>
</svg>
</button>
</div>
<label class="calendar-dropdown" data-unit="month">
<span class="calendar-dropdown-label">
<span></span>
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="m6 9 6 6 6-6"></path>
</svg>
</span>
<select class="calendar-select" aria-label="Month"></select>
</label>
<label class="calendar-dropdown" data-unit="year">
<span class="calendar-dropdown-label">
<span></span>
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="m6 9 6 6 6-6"></path>
</svg>
</span>
<select class="calendar-select" aria-label="Year"></select>
</label>
<span class="calendar-week-number-label">Week</span>
</template>
</div>
</div>
</div>
<div class="field-error" id="returnDate-error">must not be empty</div>
</div>

disabled on the input, or on a <sl:field-set> around it, disables the button.

Disabled
Thymeleaf source
<sl:date-picker name="archivedOn" aria-label="Archived on" value="2026-10-23" disabled/>
Rendered HTML
<div class="date-picker" x-data="slDatePicker" data-placeholder="Pick a date" data-range-format="{0} – {1}">
<button class="button popover-trigger date-picker-trigger" type="button" data-variant="outline" popovertarget="sl-date-picker-8mgoo640" aria-haspopup="dialog" disabled>
<span class="button-icon" data-icon="inline-start">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="M8 2v3"></path>
<path d="M16 2v3"></path>
<rect x="3" y="3" width="18" height="18" rx="2"></rect>
<path d="M3 9h18"></path>
</svg>
</span>
<span class="date-picker-value">October 23, 2026</span>
</button>
<div class="popover-content date-picker-content" id="sl-date-picker-8mgoo640" role="dialog" popover tabindex="-1" x-data="slPopover" data-align="start" aria-label="Choose a date">
<div class="calendar" x-data="slCalendar" role="group" data-locale="en" data-week-starts-on="0" data-first-week-min-days="1">
<input type="date" class="input calendar-input" name="archivedOn" aria-label="Archived on" value="2026-10-23" disabled>
<template class="calendar-template">
<div class="calendar-nav">
<button type="button" class="calendar-nav-button calendar-previous" aria-label="Go to the previous month">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="m15 18-6-6 6-6"></path>
</svg>
</button>
<button type="button" class="calendar-nav-button calendar-next" aria-label="Go to the next month">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="m9 18 6-6-6-6"></path>
</svg>
</button>
</div>
<label class="calendar-dropdown" data-unit="month">
<span class="calendar-dropdown-label">
<span></span>
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="m6 9 6 6 6-6"></path>
</svg>
</span>
<select class="calendar-select" aria-label="Month"></select>
</label>
<label class="calendar-dropdown" data-unit="year">
<span class="calendar-dropdown-label">
<span></span>
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="m6 9 6 6 6-6"></path>
</svg>
</span>
<select class="calendar-select" aria-label="Year"></select>
</label>
<span class="calendar-week-number-label">Week</span>
</template>
</div>
</div>
</div>
Key On Does
Enter, Space The button Opens the popover, with the focus on the chosen day, or else today
Arrows, Home, End, Page Up, Page Down A day Move the focus, as in the calendar
Enter, Space A day Picks it, and closes the popover once the date (or both ends of the range) is chosen
Escape The popover Closes it, and the focus goes back to the button
Tab The popover’s last stop Closes it, as the focus leaves it

The popover is a non-modal dialog, as sl:popover is. The focus is not trapped in it.

  • Back restores the date picker as htmx saved it, and slDatePicker formats the date again.
  • hx-trigger="change" on the date picker’s input (<sl:date-picker hx-post="..." hx-trigger="change">) or on the form sends the picked date.
  • An input to type the date into, the time and natural language variants are not here yet. For a time, put an <sl:input type="time"> next to the date picker.
  • Presets (a select of “Today”, “Tomorrow”, …) are not here either.
AttributeValuesDefaultDescription
placeholderstring—The button's text while no date is chosen. By default "Pick a date".
formatshort medium long fulllongHow the button shows the date, in the page's locale: as Intl.DateTimeFormat's dateStyle and Java's FormatStyle (10/23/26, Oct 23, 2026, October 23, 2026, Friday, October 23, 2026).
popover-idstring—The popover's id, which the button points at with popovertarget. By default a generated one (sl-date-picker- and eight random characters).

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

slDatePicker sits on the date picker: <div class="date-picker" x-data="slDatePicker">. Its popover has slPopover and its calendar slCalendar.

  • It needs JavaScript. Without Alpine, the button still opens the popover (popovertarget), but it holds the bare date inputs, and the button’s text does not follow them.
  • It needs no Alpine plugin. If you load Alpine yourself (shadleaf.assets.alpine=external), plain Alpine is enough.
  • Keep your own x-data off <sl:date-picker>: an element has one x-data. Listen for change on the input instead.