Date Picker
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 popover with a calendar
Section titled “A popover with a calendar”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’s text
Section titled “The button’s text”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,
slDatePickerformats it again withIntl, for the same locale, when it starts and after every change. Java andIntlboth 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 |
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>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).
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>Date of birth
Section titled “Date of birth”The calendar’s month and year selects (caption-layout="dropdown") help with dates 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>In a form
Section titled “In a form”- 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
requireddate is empty on submit, the popover opens, so the browser’s message points at the calendar. - Events. A pick fires
inputandchangeon the input, as on a calendar. - A value set by a script shows on the button once the script fires
changeon the input. - Form reset puts the button’s text back too.
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
Section titled “Disabled”disabled on the input, or on a <sl:field-set> around it, disables the button.
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>Keyboard
Section titled “Keyboard”| 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.
With htmx
Section titled “With htmx”- Back restores the date picker as htmx saved it, and
slDatePickerformats 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.
Left out
Section titled “Left out”- 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.
Attributes
Section titled “Attributes”| Attribute | Values | Default | Description |
|---|---|---|---|
placeholder | string | — | The button's text while no date is chosen. By default "Pick a date". |
format | short medium long full | long | How 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-id | string | — | 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.
JavaScript
Section titled “JavaScript”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-dataoff<sl:date-picker>: an element has onex-data. Listen forchangeon the input instead.