Combobox
Thymeleaf source
<sl:field th:field="*{framework}"> <sl:field-label>Framework</sl:field-label> <sl:combobox placeholder="Select a framework"> <sl:combobox-item value="next">Next.js</sl:combobox-item> <sl:combobox-item value="sveltekit">SvelteKit</sl:combobox-item> <sl:combobox-item value="nuxt">Nuxt.js</sl:combobox-item> <sl:combobox-item value="remix">Remix</sl:combobox-item> <sl:combobox-item value="astro">Astro</sl:combobox-item> </sl:combobox></sl:field>Rendered HTML
<div class="field"> <label class="label field-label" for="framework">Framework</label> <div class="combobox" x-data="slCombobox" data-result="1 result" data-results="{0} results"> <select class="combobox-select" id="framework" name="framework"> <option value="" hidden data-placeholder="true" selected>Select a framework</option> <option class="combobox-item" value="next">Next.js</option> <option class="combobox-item" value="sveltekit">SvelteKit</option> <option class="combobox-item" value="nuxt">Nuxt.js</option> <option class="combobox-item" value="remix">Remix</option> <option class="combobox-item" value="astro">Astro</option> </select> <svg xmlns="http://www.w3.org/2000/svg" class="icon combobox-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> <template class="combobox-template"> <div class="combobox-control"> <input class="combobox-input" type="text" role="combobox" autocomplete="off" autocapitalize="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" placeholder="Select a framework"> <button type="button" class="combobox-trigger" tabindex="-1" aria-expanded="false" aria-label="Show options"> <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> </button> </div> <div class="combobox-content" popover="manual"> <div class="combobox-list" role="listbox"></div> <div class="combobox-empty">No items found.</div> </div> <div class="combobox-status" role="status"></div> <svg xmlns="http://www.w3.org/2000/svg" class="icon combobox-item-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> </template> </div></div>Built from parts
Section titled “Built from parts”A combobox is <sl:combobox> holding <sl:combobox-item>s, optionally in <sl:combobox-group>s with
<sl:combobox-separator>s between them. Like <sl:select>, it renders a real <select> with
<option>, <optgroup> and <hr> elements, so th:field binds it as it binds any select and the form submits the
chosen value. Once Alpine runs, a text input and a list box take the select’s place; typing filters the list.
<form th:object="${project}" th:action="@{/projects}" method="post"> <sl:field th:field="*{framework}"> <sl:field-label>Framework</sl:field-label> <sl:combobox placeholder="Select a framework"> <sl:combobox-item th:each="framework : ${frameworks}" th:value="${framework.id}">[[${framework.name}]]</sl:combobox-item> </sl:combobox> <sl:field-error/> </sl:field></form>Give an item its text as content ([[${framework.name}]]): the input, the chips and the filter use it.
Combobox or select?
Section titled “Combobox or select?”<sl:select>for a short list people scan: a few to a few dozen items.<sl:combobox>for a list people would rather search: many items, items whose names people know, or several values at once (multiple). Withsearch-url, the options can come from the server, for lists too long to put in the page.<sl:native-select>to leave the list to the operating system, as phones show it.
Multiple
Section titled “Multiple”multiple lets people choose any number of items. The chosen ones are chips before the input, each with a button
that removes it; the list stays open while choosing, with a check mark on every chosen item. The select is a
<select multiple>, so th:field binds a collection (List<String>, Set<Long>, …) and the form submits one
value per chosen item. Spring’s hidden marker input comes with it, so choosing none binds an empty collection.
Thymeleaf source
<sl:field th:field="*{frameworks}"> <sl:field-label>Frameworks</sl:field-label> <sl:combobox multiple auto-highlight placeholder="Add a framework"> <sl:combobox-item value="next">Next.js</sl:combobox-item> <sl:combobox-item value="sveltekit">SvelteKit</sl:combobox-item> <sl:combobox-item value="nuxt">Nuxt.js</sl:combobox-item> <sl:combobox-item value="remix">Remix</sl:combobox-item> <sl:combobox-item value="astro">Astro</sl:combobox-item> </sl:combobox></sl:field>Rendered HTML
<div class="field"> <label class="label field-label" for="frameworks">Frameworks</label> <div class="combobox" x-data="slCombobox" data-multiple="true" data-auto-highlight="true" data-result="1 result" data-results="{0} results" data-remove-label="Remove {0}"> <input type="hidden" name="_frameworks" value="1"> <select class="combobox-select" multiple id="frameworks" name="frameworks"> <option class="combobox-item" value="next" selected>Next.js</option> <option class="combobox-item" value="sveltekit">SvelteKit</option> <option class="combobox-item" value="nuxt">Nuxt.js</option> <option class="combobox-item" value="remix">Remix</option> <option class="combobox-item" value="astro">Astro</option> </select> <template class="combobox-template"> <div class="combobox-control"> <input class="combobox-input" type="text" role="combobox" autocomplete="off" autocapitalize="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" placeholder="Add a framework"> </div> <div class="combobox-content" popover="manual"> <div class="combobox-list" role="listbox"></div> <div class="combobox-empty">No items found.</div> </div> <div class="combobox-status" role="status"></div> <span class="combobox-chip"> <span class="combobox-chip-label"></span> <button type="button" class="combobox-chip-remove" tabindex="-1"> <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="M18 6 6 18"></path> <path d="m6 6 12 12"></path> </svg> </button> </span> <svg xmlns="http://www.w3.org/2000/svg" class="icon combobox-item-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> </template> </div></div>Placeholder and clearing
Section titled “Placeholder and clearing”placeholder is the input’s text while nothing is chosen. For a single value it is also a first option with an empty
value, hidden from the list, which is chosen while the field has no value: a required combobox with nothing chosen
is invalid, as for a select.
Emptying the input and leaving it clears the choice. show-clear adds a button that does the same, in place of the
chevron while there is something to clear; in a multiple combobox it removes every chip.
Thymeleaf source
<sl:field th:field="*{stack}"> <sl:field-label>Stack</sl:field-label> <sl:combobox placeholder="Select a framework" show-clear> <sl:combobox-item value="next">Next.js</sl:combobox-item> <sl:combobox-item value="sveltekit">SvelteKit</sl:combobox-item> <sl:combobox-item value="nuxt">Nuxt.js</sl:combobox-item> </sl:combobox></sl:field>Rendered HTML
<div class="field"> <label class="label field-label" for="stack">Stack</label> <div class="combobox" x-data="slCombobox" data-result="1 result" data-results="{0} results"> <select class="combobox-select" id="stack" name="stack"> <option value="" hidden data-placeholder="true">Select a framework</option> <option class="combobox-item" value="next">Next.js</option> <option class="combobox-item" value="sveltekit" selected>SvelteKit</option> <option class="combobox-item" value="nuxt">Nuxt.js</option> </select> <svg xmlns="http://www.w3.org/2000/svg" class="icon combobox-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> <template class="combobox-template"> <div class="combobox-control"> <input class="combobox-input" type="text" role="combobox" autocomplete="off" autocapitalize="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" placeholder="Select a framework"> <button type="button" class="combobox-clear" tabindex="-1" hidden aria-label="Clear"> <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="M18 6 6 18"></path> <path d="m6 6 12 12"></path> </svg> </button> <button type="button" class="combobox-trigger" tabindex="-1" aria-expanded="false" aria-label="Show options"> <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> </button> </div> <div class="combobox-content" popover="manual"> <div class="combobox-list" role="listbox"></div> <div class="combobox-empty">No items found.</div> </div> <div class="combobox-status" role="status"></div> <svg xmlns="http://www.w3.org/2000/svg" class="icon combobox-item-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> </template> </div></div>Groups, separators and icons
Section titled “Groups, separators and icons”<sl:combobox-group label="..."> is an <optgroup>: its label shows above its items, and hides while none of them
match what was typed. <sl:combobox-separator/> is an <hr>, hidden while typing.
Thymeleaf source
<sl:field th:field="*{meetingZone}"> <sl:field-label>Timezone</sl:field-label> <sl:combobox placeholder="Select a timezone"> <sl:combobox-group label="Americas"> <sl:combobox-item value="Pacific/Honolulu">(GMT-10) Honolulu</sl:combobox-item> <sl:combobox-item value="America/Anchorage">(GMT-9) Anchorage</sl:combobox-item> <sl:combobox-item value="America/Los_Angeles">(GMT-8) Los Angeles</sl:combobox-item> <sl:combobox-item value="America/Tijuana">(GMT-8) Tijuana</sl:combobox-item> <sl:combobox-item value="America/Vancouver">(GMT-8) Vancouver</sl:combobox-item> <sl:combobox-item value="America/Denver">(GMT-7) Denver</sl:combobox-item> <sl:combobox-item value="America/Edmonton">(GMT-7) Edmonton</sl:combobox-item> <sl:combobox-item value="America/Phoenix">(GMT-7) Phoenix</sl:combobox-item> <sl:combobox-item value="America/Chicago">(GMT-6) Chicago</sl:combobox-item> <sl:combobox-item value="America/Guatemala">(GMT-6) Guatemala</sl:combobox-item> <sl:combobox-item value="America/Mexico_City">(GMT-6) Mexico City</sl:combobox-item> <sl:combobox-item value="America/Winnipeg">(GMT-6) Winnipeg</sl:combobox-item> <sl:combobox-item value="America/Bogota">(GMT-5) Bogota</sl:combobox-item> <sl:combobox-item value="America/Havana">(GMT-5) Havana</sl:combobox-item> <sl:combobox-item value="America/Lima">(GMT-5) Lima</sl:combobox-item> <sl:combobox-item value="America/New_York">(GMT-5) New York</sl:combobox-item> <sl:combobox-item value="America/Toronto">(GMT-5) Toronto</sl:combobox-item> <sl:combobox-item value="America/Caracas">(GMT-4) Caracas</sl:combobox-item> <sl:combobox-item value="America/Halifax">(GMT-4) Halifax</sl:combobox-item> <sl:combobox-item value="America/La_Paz">(GMT-4) La Paz</sl:combobox-item> <sl:combobox-item value="America/Santiago">(GMT-4) Santiago</sl:combobox-item> <sl:combobox-item value="America/St_Johns">(GMT-3:30) St. John's</sl:combobox-item> <sl:combobox-item value="America/Argentina/Buenos_Aires">(GMT-3) Buenos Aires</sl:combobox-item> <sl:combobox-item value="America/Montevideo">(GMT-3) Montevideo</sl:combobox-item> <sl:combobox-item value="America/Sao_Paulo">(GMT-3) São Paulo</sl:combobox-item> </sl:combobox-group> <sl:combobox-separator/> <sl:combobox-group label="Europe"> <sl:combobox-item value="Europe/Dublin">(GMT+0) Dublin</sl:combobox-item> <sl:combobox-item value="Europe/Lisbon">(GMT+0) Lisbon</sl:combobox-item> <sl:combobox-item value="Europe/London">(GMT+0) London</sl:combobox-item> <sl:combobox-item value="Atlantic/Reykjavik">(GMT+0) Reykjavik</sl:combobox-item> <sl:combobox-item value="Europe/Amsterdam">(GMT+1) Amsterdam</sl:combobox-item> <sl:combobox-item value="Europe/Berlin">(GMT+1) Berlin</sl:combobox-item> <sl:combobox-item value="Europe/Brussels">(GMT+1) Brussels</sl:combobox-item> <sl:combobox-item value="Europe/Budapest">(GMT+1) Budapest</sl:combobox-item> <sl:combobox-item value="Europe/Copenhagen">(GMT+1) Copenhagen</sl:combobox-item> <sl:combobox-item value="Europe/Madrid">(GMT+1) Madrid</sl:combobox-item> <sl:combobox-item value="Europe/Oslo">(GMT+1) Oslo</sl:combobox-item> <sl:combobox-item value="Europe/Paris">(GMT+1) Paris</sl:combobox-item> <sl:combobox-item value="Europe/Prague">(GMT+1) Prague</sl:combobox-item> <sl:combobox-item value="Europe/Rome">(GMT+1) Rome</sl:combobox-item> <sl:combobox-item value="Europe/Stockholm">(GMT+1) Stockholm</sl:combobox-item> <sl:combobox-item value="Europe/Vienna">(GMT+1) Vienna</sl:combobox-item> <sl:combobox-item value="Europe/Warsaw">(GMT+1) Warsaw</sl:combobox-item> <sl:combobox-item value="Europe/Zurich">(GMT+1) Zurich</sl:combobox-item> <sl:combobox-item value="Europe/Athens">(GMT+2) Athens</sl:combobox-item> <sl:combobox-item value="Europe/Bucharest">(GMT+2) Bucharest</sl:combobox-item> <sl:combobox-item value="Europe/Helsinki">(GMT+2) Helsinki</sl:combobox-item> <sl:combobox-item value="Europe/Kyiv">(GMT+2) Kyiv</sl:combobox-item> <sl:combobox-item value="Europe/Istanbul">(GMT+3) Istanbul</sl:combobox-item> <sl:combobox-item value="Europe/Moscow">(GMT+3) Moscow</sl:combobox-item> </sl:combobox-group> <sl:combobox-separator/> <sl:combobox-group label="Africa"> <sl:combobox-item value="Africa/Accra">(GMT+0) Accra</sl:combobox-item> <sl:combobox-item value="Africa/Casablanca">(GMT+0) Casablanca</sl:combobox-item> <sl:combobox-item value="Africa/Algiers">(GMT+1) Algiers</sl:combobox-item> <sl:combobox-item value="Africa/Kinshasa">(GMT+1) Kinshasa</sl:combobox-item> <sl:combobox-item value="Africa/Lagos">(GMT+1) Lagos</sl:combobox-item> <sl:combobox-item value="Africa/Cairo">(GMT+2) Cairo</sl:combobox-item> <sl:combobox-item value="Africa/Johannesburg">(GMT+2) Johannesburg</sl:combobox-item> <sl:combobox-item value="Africa/Addis_Ababa">(GMT+3) Addis Ababa</sl:combobox-item> <sl:combobox-item value="Africa/Nairobi">(GMT+3) Nairobi</sl:combobox-item> </sl:combobox-group> <sl:combobox-separator/> <sl:combobox-group label="Asia"> <sl:combobox-item value="Asia/Jerusalem">(GMT+2) Jerusalem</sl:combobox-item> <sl:combobox-item value="Asia/Riyadh">(GMT+3) Riyadh</sl:combobox-item> <sl:combobox-item value="Asia/Tehran">(GMT+3:30) Tehran</sl:combobox-item> <sl:combobox-item value="Asia/Dubai">(GMT+4) Dubai</sl:combobox-item> <sl:combobox-item value="Asia/Karachi">(GMT+5) Karachi</sl:combobox-item> <sl:combobox-item value="Asia/Kolkata">(GMT+5:30) Kolkata</sl:combobox-item> <sl:combobox-item value="Asia/Kathmandu">(GMT+5:45) Kathmandu</sl:combobox-item> <sl:combobox-item value="Asia/Dhaka">(GMT+6) Dhaka</sl:combobox-item> <sl:combobox-item value="Asia/Bangkok">(GMT+7) Bangkok</sl:combobox-item> <sl:combobox-item value="Asia/Ho_Chi_Minh">(GMT+7) Ho Chi Minh City</sl:combobox-item> <sl:combobox-item value="Asia/Jakarta">(GMT+7) Jakarta</sl:combobox-item> <sl:combobox-item value="Asia/Hong_Kong">(GMT+8) Hong Kong</sl:combobox-item> <sl:combobox-item value="Asia/Manila">(GMT+8) Manila</sl:combobox-item> <sl:combobox-item value="Asia/Shanghai">(GMT+8) Shanghai</sl:combobox-item> <sl:combobox-item value="Asia/Singapore">(GMT+8) Singapore</sl:combobox-item> <sl:combobox-item value="Asia/Taipei">(GMT+8) Taipei</sl:combobox-item> <sl:combobox-item value="Asia/Seoul">(GMT+9) Seoul</sl:combobox-item> <sl:combobox-item value="Asia/Tokyo">(GMT+9) Tokyo</sl:combobox-item> </sl:combobox-group> <sl:combobox-separator/> <sl:combobox-group label="Pacific"> <sl:combobox-item value="Australia/Perth">(GMT+8) Perth</sl:combobox-item> <sl:combobox-item value="Australia/Adelaide">(GMT+9:30) Adelaide</sl:combobox-item> <sl:combobox-item value="Australia/Brisbane">(GMT+10) Brisbane</sl:combobox-item> <sl:combobox-item value="Australia/Melbourne">(GMT+10) Melbourne</sl:combobox-item> <sl:combobox-item value="Pacific/Port_Moresby">(GMT+10) Port Moresby</sl:combobox-item> <sl:combobox-item value="Australia/Sydney">(GMT+10) Sydney</sl:combobox-item> <sl:combobox-item value="Pacific/Auckland">(GMT+12) Auckland</sl:combobox-item> <sl:combobox-item value="Pacific/Fiji">(GMT+12) Fiji</sl:combobox-item> </sl:combobox-group> </sl:combobox></sl:field>Rendered HTML
<div class="field"> <label class="label field-label" for="meetingZone">Timezone</label> <div class="combobox" x-data="slCombobox" data-result="1 result" data-results="{0} results"> <select class="combobox-select" id="meetingZone" name="meetingZone"> <option value="" hidden data-placeholder="true" selected>Select a timezone</option> <optgroup class="combobox-group" label="Americas"> <option class="combobox-item" value="Pacific/Honolulu">(GMT-10) Honolulu</option> <option class="combobox-item" value="America/Anchorage">(GMT-9) Anchorage</option> <option class="combobox-item" value="America/Los_Angeles">(GMT-8) Los Angeles</option> <option class="combobox-item" value="America/Tijuana">(GMT-8) Tijuana</option> <option class="combobox-item" value="America/Vancouver">(GMT-8) Vancouver</option> <option class="combobox-item" value="America/Denver">(GMT-7) Denver</option> <option class="combobox-item" value="America/Edmonton">(GMT-7) Edmonton</option> <option class="combobox-item" value="America/Phoenix">(GMT-7) Phoenix</option> <option class="combobox-item" value="America/Chicago">(GMT-6) Chicago</option> <option class="combobox-item" value="America/Guatemala">(GMT-6) Guatemala</option> <option class="combobox-item" value="America/Mexico_City">(GMT-6) Mexico City</option> <option class="combobox-item" value="America/Winnipeg">(GMT-6) Winnipeg</option> <option class="combobox-item" value="America/Bogota">(GMT-5) Bogota</option> <option class="combobox-item" value="America/Havana">(GMT-5) Havana</option> <option class="combobox-item" value="America/Lima">(GMT-5) Lima</option> <option class="combobox-item" value="America/New_York">(GMT-5) New York</option> <option class="combobox-item" value="America/Toronto">(GMT-5) Toronto</option> <option class="combobox-item" value="America/Caracas">(GMT-4) Caracas</option> <option class="combobox-item" value="America/Halifax">(GMT-4) Halifax</option> <option class="combobox-item" value="America/La_Paz">(GMT-4) La Paz</option> <option class="combobox-item" value="America/Santiago">(GMT-4) Santiago</option> <option class="combobox-item" value="America/St_Johns">(GMT-3:30) St. John's</option> <option class="combobox-item" value="America/Argentina/Buenos_Aires">(GMT-3) Buenos Aires</option> <option class="combobox-item" value="America/Montevideo">(GMT-3) Montevideo</option> <option class="combobox-item" value="America/Sao_Paulo">(GMT-3) São Paulo</option> </optgroup> <hr class="combobox-separator"> <optgroup class="combobox-group" label="Europe"> <option class="combobox-item" value="Europe/Dublin">(GMT+0) Dublin</option> <option class="combobox-item" value="Europe/Lisbon">(GMT+0) Lisbon</option> <option class="combobox-item" value="Europe/London">(GMT+0) London</option> <option class="combobox-item" value="Atlantic/Reykjavik">(GMT+0) Reykjavik</option> <option class="combobox-item" value="Europe/Amsterdam">(GMT+1) Amsterdam</option> <option class="combobox-item" value="Europe/Berlin">(GMT+1) Berlin</option> <option class="combobox-item" value="Europe/Brussels">(GMT+1) Brussels</option> <option class="combobox-item" value="Europe/Budapest">(GMT+1) Budapest</option> <option class="combobox-item" value="Europe/Copenhagen">(GMT+1) Copenhagen</option> <option class="combobox-item" value="Europe/Madrid">(GMT+1) Madrid</option> <option class="combobox-item" value="Europe/Oslo">(GMT+1) Oslo</option> <option class="combobox-item" value="Europe/Paris">(GMT+1) Paris</option> <option class="combobox-item" value="Europe/Prague">(GMT+1) Prague</option> <option class="combobox-item" value="Europe/Rome">(GMT+1) Rome</option> <option class="combobox-item" value="Europe/Stockholm">(GMT+1) Stockholm</option> <option class="combobox-item" value="Europe/Vienna">(GMT+1) Vienna</option> <option class="combobox-item" value="Europe/Warsaw">(GMT+1) Warsaw</option> <option class="combobox-item" value="Europe/Zurich">(GMT+1) Zurich</option> <option class="combobox-item" value="Europe/Athens">(GMT+2) Athens</option> <option class="combobox-item" value="Europe/Bucharest">(GMT+2) Bucharest</option> <option class="combobox-item" value="Europe/Helsinki">(GMT+2) Helsinki</option> <option class="combobox-item" value="Europe/Kyiv">(GMT+2) Kyiv</option> <option class="combobox-item" value="Europe/Istanbul">(GMT+3) Istanbul</option> <option class="combobox-item" value="Europe/Moscow">(GMT+3) Moscow</option> </optgroup> <hr class="combobox-separator"> <optgroup class="combobox-group" label="Africa"> <option class="combobox-item" value="Africa/Accra">(GMT+0) Accra</option> <option class="combobox-item" value="Africa/Casablanca">(GMT+0) Casablanca</option> <option class="combobox-item" value="Africa/Algiers">(GMT+1) Algiers</option> <option class="combobox-item" value="Africa/Kinshasa">(GMT+1) Kinshasa</option> <option class="combobox-item" value="Africa/Lagos">(GMT+1) Lagos</option> <option class="combobox-item" value="Africa/Cairo">(GMT+2) Cairo</option> <option class="combobox-item" value="Africa/Johannesburg">(GMT+2) Johannesburg</option> <option class="combobox-item" value="Africa/Addis_Ababa">(GMT+3) Addis Ababa</option> <option class="combobox-item" value="Africa/Nairobi">(GMT+3) Nairobi</option> </optgroup> <hr class="combobox-separator"> <optgroup class="combobox-group" label="Asia"> <option class="combobox-item" value="Asia/Jerusalem">(GMT+2) Jerusalem</option> <option class="combobox-item" value="Asia/Riyadh">(GMT+3) Riyadh</option> <option class="combobox-item" value="Asia/Tehran">(GMT+3:30) Tehran</option> <option class="combobox-item" value="Asia/Dubai">(GMT+4) Dubai</option> <option class="combobox-item" value="Asia/Karachi">(GMT+5) Karachi</option> <option class="combobox-item" value="Asia/Kolkata">(GMT+5:30) Kolkata</option> <option class="combobox-item" value="Asia/Kathmandu">(GMT+5:45) Kathmandu</option> <option class="combobox-item" value="Asia/Dhaka">(GMT+6) Dhaka</option> <option class="combobox-item" value="Asia/Bangkok">(GMT+7) Bangkok</option> <option class="combobox-item" value="Asia/Ho_Chi_Minh">(GMT+7) Ho Chi Minh City</option> <option class="combobox-item" value="Asia/Jakarta">(GMT+7) Jakarta</option> <option class="combobox-item" value="Asia/Hong_Kong">(GMT+8) Hong Kong</option> <option class="combobox-item" value="Asia/Manila">(GMT+8) Manila</option> <option class="combobox-item" value="Asia/Shanghai">(GMT+8) Shanghai</option> <option class="combobox-item" value="Asia/Singapore">(GMT+8) Singapore</option> <option class="combobox-item" value="Asia/Taipei">(GMT+8) Taipei</option> <option class="combobox-item" value="Asia/Seoul">(GMT+9) Seoul</option> <option class="combobox-item" value="Asia/Tokyo">(GMT+9) Tokyo</option> </optgroup> <hr class="combobox-separator"> <optgroup class="combobox-group" label="Pacific"> <option class="combobox-item" value="Australia/Perth">(GMT+8) Perth</option> <option class="combobox-item" value="Australia/Adelaide">(GMT+9:30) Adelaide</option> <option class="combobox-item" value="Australia/Brisbane">(GMT+10) Brisbane</option> <option class="combobox-item" value="Australia/Melbourne">(GMT+10) Melbourne</option> <option class="combobox-item" value="Pacific/Port_Moresby">(GMT+10) Port Moresby</option> <option class="combobox-item" value="Australia/Sydney">(GMT+10) Sydney</option> <option class="combobox-item" value="Pacific/Auckland">(GMT+12) Auckland</option> <option class="combobox-item" value="Pacific/Fiji">(GMT+12) Fiji</option> </optgroup> </select> <svg xmlns="http://www.w3.org/2000/svg" class="icon combobox-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> <template class="combobox-template"> <div class="combobox-control"> <input class="combobox-input" type="text" role="combobox" autocomplete="off" autocapitalize="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" placeholder="Select a timezone"> <button type="button" class="combobox-trigger" tabindex="-1" aria-expanded="false" aria-label="Show options"> <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> </button> </div> <div class="combobox-content" popover="manual"> <div class="combobox-list" role="listbox"></div> <div class="combobox-empty">No items found.</div> </div> <div class="combobox-status" role="status"></div> <svg xmlns="http://www.w3.org/2000/svg" class="icon combobox-item-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> </template> </div></div>An item may start with an icon. The list shows it; the input and the chips show the item’s text.
Thymeleaf source
<sl:field th:field="*{issueStatus}"> <sl:field-label>Status</sl:field-label> <sl:combobox placeholder="Set a status"> <sl:combobox-item value="backlog"><sl:icon name="circle-dashed"/>Backlog</sl:combobox-item> <sl:combobox-item value="todo"><sl:icon name="circle"/>Todo</sl:combobox-item> <sl:combobox-item value="in-progress"><sl:icon name="circle-dot"/>In progress</sl:combobox-item> <sl:combobox-item value="done"><sl:icon name="circle-check"/>Done</sl:combobox-item> </sl:combobox></sl:field>Rendered HTML
<div class="field"> <label class="label field-label" for="issueStatus">Status</label> <div class="combobox" x-data="slCombobox" data-result="1 result" data-results="{0} results"> <select class="combobox-select" id="issueStatus" name="issueStatus"> <option value="" hidden data-placeholder="true">Set a status</option> <option class="combobox-item" value="backlog"> <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="M10.1 2.182a10 10 0 0 1 3.8 0"></path> <path d="M13.9 21.818a10 10 0 0 1-3.8 0"></path> <path d="M17.609 3.721a10 10 0 0 1 2.69 2.7"></path> <path d="M2.182 13.9a10 10 0 0 1 0-3.8"></path> <path d="M20.279 17.609a10 10 0 0 1-2.7 2.69"></path> <path d="M21.818 10.1a10 10 0 0 1 0 3.8"></path> <path d="M3.721 6.391a10 10 0 0 1 2.7-2.69"></path> <path d="M6.391 20.279a10 10 0 0 1-2.69-2.7"></path> </svg> Backlog </option> <option class="combobox-item" value="todo" selected> <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"> <circle cx="12" cy="12" r="10"></circle> </svg> Todo </option> <option class="combobox-item" value="in-progress"> <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"> <circle cx="12" cy="12" r="1"></circle> <circle cx="12" cy="12" r="10"></circle> </svg> In progress </option> <option class="combobox-item" value="done"> <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"> <circle cx="12" cy="12" r="10"></circle> <path d="m16 9-5.5 5.5L8 12"></path> </svg> Done </option> </select> <svg xmlns="http://www.w3.org/2000/svg" class="icon combobox-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> <template class="combobox-template"> <div class="combobox-control"> <input class="combobox-input" type="text" role="combobox" autocomplete="off" autocapitalize="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" placeholder="Set a status"> <button type="button" class="combobox-trigger" tabindex="-1" aria-expanded="false" aria-label="Show options"> <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> </button> </div> <div class="combobox-content" popover="manual"> <div class="combobox-list" role="listbox"></div> <div class="combobox-empty">No items found.</div> </div> <div class="combobox-status" role="status"></div> <svg xmlns="http://www.w3.org/2000/svg" class="icon combobox-item-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> </template> </div></div>Filtering and auto-highlight
Section titled “Filtering and auto-highlight”The list shows the items whose text contains what was typed, ignoring case and accents (turk finds Türkiye). By
default nothing is highlighted while typing, so Enter does not choose by accident; auto-highlight highlights the
first match, so typing a few letters and pressing Enter chooses it. Long lists scroll; the list never grows taller
than the window.
Thymeleaf source
<sl:field th:field="*{destination}"> <sl:field-label>Country</sl:field-label> <sl:combobox placeholder="Select a country" auto-highlight> <sl:combobox-item value="argentina">Argentina</sl:combobox-item> <sl:combobox-item value="australia">Australia</sl:combobox-item> <sl:combobox-item value="austria">Austria</sl:combobox-item> <sl:combobox-item value="belgium">Belgium</sl:combobox-item> <sl:combobox-item value="brazil">Brazil</sl:combobox-item> <sl:combobox-item value="canada">Canada</sl:combobox-item> <sl:combobox-item value="chile">Chile</sl:combobox-item> <sl:combobox-item value="china">China</sl:combobox-item> <sl:combobox-item value="colombia">Colombia</sl:combobox-item> <sl:combobox-item value="czechia">Czechia</sl:combobox-item> <sl:combobox-item value="denmark">Denmark</sl:combobox-item> <sl:combobox-item value="egypt">Egypt</sl:combobox-item> <sl:combobox-item value="finland">Finland</sl:combobox-item> <sl:combobox-item value="france">France</sl:combobox-item> <sl:combobox-item value="germany">Germany</sl:combobox-item> <sl:combobox-item value="greece">Greece</sl:combobox-item> <sl:combobox-item value="india">India</sl:combobox-item> <sl:combobox-item value="indonesia">Indonesia</sl:combobox-item> <sl:combobox-item value="ireland">Ireland</sl:combobox-item> <sl:combobox-item value="italy">Italy</sl:combobox-item> <sl:combobox-item value="japan">Japan</sl:combobox-item> <sl:combobox-item value="kenya">Kenya</sl:combobox-item> <sl:combobox-item value="mexico">Mexico</sl:combobox-item> <sl:combobox-item value="morocco">Morocco</sl:combobox-item> <sl:combobox-item value="netherlands">Netherlands</sl:combobox-item> <sl:combobox-item value="new-zealand">New Zealand</sl:combobox-item> <sl:combobox-item value="nigeria">Nigeria</sl:combobox-item> <sl:combobox-item value="norway">Norway</sl:combobox-item> <sl:combobox-item value="peru">Peru</sl:combobox-item> <sl:combobox-item value="poland">Poland</sl:combobox-item> <sl:combobox-item value="portugal">Portugal</sl:combobox-item> <sl:combobox-item value="south-africa">South Africa</sl:combobox-item> <sl:combobox-item value="south-korea">South Korea</sl:combobox-item> <sl:combobox-item value="spain">Spain</sl:combobox-item> <sl:combobox-item value="sweden">Sweden</sl:combobox-item> <sl:combobox-item value="switzerland">Switzerland</sl:combobox-item> <sl:combobox-item value="thailand">Thailand</sl:combobox-item> <sl:combobox-item value="turkiye">Türkiye</sl:combobox-item> <sl:combobox-item value="united-kingdom">United Kingdom</sl:combobox-item> <sl:combobox-item value="united-states">United States</sl:combobox-item> <sl:combobox-item value="vietnam">Vietnam</sl:combobox-item> </sl:combobox></sl:field>Rendered HTML
<div class="field"> <label class="label field-label" for="destination">Country</label> <div class="combobox" x-data="slCombobox" data-auto-highlight="true" data-result="1 result" data-results="{0} results"> <select class="combobox-select" id="destination" name="destination"> <option value="" hidden data-placeholder="true" selected>Select a country</option> <option class="combobox-item" value="argentina">Argentina</option> <option class="combobox-item" value="australia">Australia</option> <option class="combobox-item" value="austria">Austria</option> <option class="combobox-item" value="belgium">Belgium</option> <option class="combobox-item" value="brazil">Brazil</option> <option class="combobox-item" value="canada">Canada</option> <option class="combobox-item" value="chile">Chile</option> <option class="combobox-item" value="china">China</option> <option class="combobox-item" value="colombia">Colombia</option> <option class="combobox-item" value="czechia">Czechia</option> <option class="combobox-item" value="denmark">Denmark</option> <option class="combobox-item" value="egypt">Egypt</option> <option class="combobox-item" value="finland">Finland</option> <option class="combobox-item" value="france">France</option> <option class="combobox-item" value="germany">Germany</option> <option class="combobox-item" value="greece">Greece</option> <option class="combobox-item" value="india">India</option> <option class="combobox-item" value="indonesia">Indonesia</option> <option class="combobox-item" value="ireland">Ireland</option> <option class="combobox-item" value="italy">Italy</option> <option class="combobox-item" value="japan">Japan</option> <option class="combobox-item" value="kenya">Kenya</option> <option class="combobox-item" value="mexico">Mexico</option> <option class="combobox-item" value="morocco">Morocco</option> <option class="combobox-item" value="netherlands">Netherlands</option> <option class="combobox-item" value="new-zealand">New Zealand</option> <option class="combobox-item" value="nigeria">Nigeria</option> <option class="combobox-item" value="norway">Norway</option> <option class="combobox-item" value="peru">Peru</option> <option class="combobox-item" value="poland">Poland</option> <option class="combobox-item" value="portugal">Portugal</option> <option class="combobox-item" value="south-africa">South Africa</option> <option class="combobox-item" value="south-korea">South Korea</option> <option class="combobox-item" value="spain">Spain</option> <option class="combobox-item" value="sweden">Sweden</option> <option class="combobox-item" value="switzerland">Switzerland</option> <option class="combobox-item" value="thailand">Thailand</option> <option class="combobox-item" value="turkiye">Türkiye</option> <option class="combobox-item" value="united-kingdom">United Kingdom</option> <option class="combobox-item" value="united-states">United States</option> <option class="combobox-item" value="vietnam">Vietnam</option> </select> <svg xmlns="http://www.w3.org/2000/svg" class="icon combobox-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> <template class="combobox-template"> <div class="combobox-control"> <input class="combobox-input" type="text" role="combobox" autocomplete="off" autocapitalize="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" placeholder="Select a country"> <button type="button" class="combobox-trigger" tabindex="-1" aria-expanded="false" aria-label="Show options"> <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> </button> </div> <div class="combobox-content" popover="manual"> <div class="combobox-list" role="listbox"></div> <div class="combobox-empty">No items found.</div> </div> <div class="combobox-status" role="status"></div> <svg xmlns="http://www.w3.org/2000/svg" class="icon combobox-item-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> </template> </div></div>Options from the server
Section titled “Options from the server”search-url makes the combobox ask the server for the options as people type, for lists too long to put in the page.
300 ms after the last key, it sends a GET to that URL with the text as q, and expects the HTML of
<sl:combobox-item>s (and groups and separators, if you like) back. A Thymeleaf fragment renders them:
<sl:combobox th:field="*{country}" placeholder="Search a country" th:search-url="@{/countries/search}"> <!-- The chosen country, which the controller looked up: the combobox shows it before anyone types. --> <sl:combobox-item th:if="${chosenCountry != null}" th:value="${chosenCountry.code}">[[${chosenCountry.name}]]</sl:combobox-item></sl:combobox>@GetMapping("/countries/search")String searchCountries(@RequestParam String q, Model model) { model.addAttribute("countries", countryService.search(q, 20)); return "countries :: options";}<th:block th:fragment="options"> <sl:combobox-item th:each="country : ${countries}" th:value="${country.code}">[[${country.name}]]</sl:combobox-item></th:block>- The answer replaces the options that are not chosen. A chosen option stays, so the value does not change; when the answer leaves it out, it stays hidden from the list. Render the chosen option(s) in the page, as above, so the combobox can show and submit them before anyone types.
- The server filters. The browser shows what the answer holds, without filtering it again; an empty answer shows
“No items found.” (or
empty). Limit the number of results on the server. - An empty input brings back the options the page came with: render a first few there, or none.
- A newer query aborts the request before it, so a slow answer never replaces a newer one. While waiting, the list says “Searching…”; an answer that is not a 2xx, or that was redirected (a session that expired and sent the request to the login page), shows “The options could not be loaded.”
- To add parameters, such as another field’s value for options that depend on it, listen for
sl-combobox-searchon the select. It fires before each request, withdetail.queryanddetail.url, aURLwhosesearchParamsyou can change;preventDefault()skips the request.
document.addEventListener('sl-combobox-search', event => { if (event.target.id === 'city') { event.detail.url.searchParams.set('country', document.getElementById('country').value); }});Searching needs JavaScript. Without it, the combobox is a select with the options in the page, so only those can be chosen.
The combobox sits in a wrapper that also holds the input and the list. class goes to that wrapper, so
class="w-full" widens it; every other attribute goes to the <select>. The list is at least as wide as the input.
In a form
Section titled “In a form”th:field, on the combobox or on its field, binds it. In a field, the select takes the field’sid(the label points at it) andaria-describedby; the input takes them over from the select, so the label names the input and the description and error describe it.- Errors. A field with errors marks the combobox
aria-invalid="true", and<sl:form-errors autofocus/>gives itautofocuswhen it is the first control with errors; the focus goes on to the input. - No free text. Only the options can be chosen: what was typed without choosing is not submitted, and the input
shows the choice again when it loses the focus. For a text field with suggestions, use an
input with a
<datalist>. - Events. Choosing fires
inputandchangeon the<select>, sohx-trigger="change"and your own listeners work as for any select. A script that changes the select’s options or value should firechangeitself; the input and the chips follow. - Form reset puts the combobox back to its rendered value.
Thymeleaf source
<sl:field th:field="*{language}"> <sl:field-label>Language</sl:field-label> <sl:combobox placeholder="Select a language"> <sl:combobox-item value="java">Java</sl:combobox-item> <sl:combobox-item value="kotlin">Kotlin</sl:combobox-item> <sl:combobox-item value="groovy">Groovy</sl:combobox-item> </sl:combobox> <sl:field-error/></sl:field><sl:field th:field="*{languages}"> <sl:field-label>Other languages</sl:field-label> <sl:combobox multiple placeholder="Add a language"> <sl:combobox-item value="java">Java</sl:combobox-item> <sl:combobox-item value="kotlin">Kotlin</sl:combobox-item> <sl:combobox-item value="groovy">Groovy</sl:combobox-item> </sl:combobox> <sl:field-error/></sl:field>Rendered HTML
<div class="field" data-invalid="true"> <label class="label field-label" for="language">Language</label> <div class="combobox" x-data="slCombobox" data-result="1 result" data-results="{0} results"> <select class="combobox-select" aria-invalid="true" id="language" aria-describedby="language-error" name="language"> <option value="" hidden data-placeholder="true" selected>Select a language</option> <option class="combobox-item" value="java">Java</option> <option class="combobox-item" value="kotlin">Kotlin</option> <option class="combobox-item" value="groovy">Groovy</option> </select> <svg xmlns="http://www.w3.org/2000/svg" class="icon combobox-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> <template class="combobox-template"> <div class="combobox-control"> <input class="combobox-input" type="text" role="combobox" autocomplete="off" autocapitalize="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" placeholder="Select a language"> <button type="button" class="combobox-trigger" tabindex="-1" aria-expanded="false" aria-label="Show options"> <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> </button> </div> <div class="combobox-content" popover="manual"> <div class="combobox-list" role="listbox"></div> <div class="combobox-empty">No items found.</div> </div> <div class="combobox-status" role="status"></div> <svg xmlns="http://www.w3.org/2000/svg" class="icon combobox-item-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> </template> </div> <div class="field-error" id="language-error">must not be empty</div></div><div class="field" data-invalid="true"> <label class="label field-label" for="languages">Other languages</label> <div class="combobox" x-data="slCombobox" data-multiple="true" data-result="1 result" data-results="{0} results" data-remove-label="Remove {0}"> <input type="hidden" name="_languages" value="1"> <select class="combobox-select" multiple aria-invalid="true" id="languages" aria-describedby="languages-error" name="languages"> <option class="combobox-item" value="java">Java</option> <option class="combobox-item" value="kotlin">Kotlin</option> <option class="combobox-item" value="groovy">Groovy</option> </select> <template class="combobox-template"> <div class="combobox-control"> <input class="combobox-input" type="text" role="combobox" autocomplete="off" autocapitalize="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" placeholder="Add a language"> </div> <div class="combobox-content" popover="manual"> <div class="combobox-list" role="listbox"></div> <div class="combobox-empty">No items found.</div> </div> <div class="combobox-status" role="status"></div> <span class="combobox-chip"> <span class="combobox-chip-label"></span> <button type="button" class="combobox-chip-remove" tabindex="-1"> <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="M18 6 6 18"></path> <path d="m6 6 12 12"></path> </svg> </button> </span> <svg xmlns="http://www.w3.org/2000/svg" class="icon combobox-item-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> </template> </div> <div class="field-error" id="languages-error">pick at least one</div></div>Disabled
Section titled “Disabled”disabled on the combobox, or on a <sl:field-set> around it, disables it: it fades with its chips. disabled on an
item leaves it in the list, faded; the keyboard and the pointer skip it.
Thymeleaf source
<sl:field th:field="*{runtime}"> <sl:field-label>Runtime</sl:field-label> <sl:combobox disabled> <sl:combobox-item value="jvm">JVM</sl:combobox-item> </sl:combobox></sl:field><sl:field th:field="*{plugins}"> <sl:field-label>Plugins</sl:field-label> <sl:combobox multiple disabled> <sl:combobox-item value="lombok">Lombok</sl:combobox-item> <sl:combobox-item value="mapstruct">MapStruct</sl:combobox-item> </sl:combobox></sl:field><sl:field th:field="*{build}"> <sl:field-label>Build tool</sl:field-label> <sl:combobox placeholder="Select a build tool"> <sl:combobox-item value="maven">Maven</sl:combobox-item> <sl:combobox-item value="ant" disabled>Ant</sl:combobox-item> <sl:combobox-item value="gradle">Gradle</sl:combobox-item> </sl:combobox></sl:field>Rendered HTML
<div class="field"> <label class="label field-label" for="runtime">Runtime</label> <div class="combobox" x-data="slCombobox" data-result="1 result" data-results="{0} results"> <select class="combobox-select" disabled id="runtime" name="runtime"> <option value="" hidden data-placeholder="true"></option> <option class="combobox-item" value="jvm" selected>JVM</option> </select> <svg xmlns="http://www.w3.org/2000/svg" class="icon combobox-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> <template class="combobox-template"> <div class="combobox-control"> <input class="combobox-input" type="text" role="combobox" autocomplete="off" autocapitalize="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false"> <button type="button" class="combobox-trigger" tabindex="-1" aria-expanded="false" aria-label="Show options"> <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> </button> </div> <div class="combobox-content" popover="manual"> <div class="combobox-list" role="listbox"></div> <div class="combobox-empty">No items found.</div> </div> <div class="combobox-status" role="status"></div> <svg xmlns="http://www.w3.org/2000/svg" class="icon combobox-item-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> </template> </div></div><div class="field"> <label class="label field-label" for="plugins">Plugins</label> <div class="combobox" x-data="slCombobox" data-multiple="true" data-result="1 result" data-results="{0} results" data-remove-label="Remove {0}"> <select class="combobox-select" multiple disabled id="plugins" name="plugins"> <option class="combobox-item" value="lombok" selected>Lombok</option> <option class="combobox-item" value="mapstruct" selected>MapStruct</option> </select> <template class="combobox-template"> <div class="combobox-control"> <input class="combobox-input" type="text" role="combobox" autocomplete="off" autocapitalize="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false"> </div> <div class="combobox-content" popover="manual"> <div class="combobox-list" role="listbox"></div> <div class="combobox-empty">No items found.</div> </div> <div class="combobox-status" role="status"></div> <span class="combobox-chip"> <span class="combobox-chip-label"></span> <button type="button" class="combobox-chip-remove" tabindex="-1"> <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="M18 6 6 18"></path> <path d="m6 6 12 12"></path> </svg> </button> </span> <svg xmlns="http://www.w3.org/2000/svg" class="icon combobox-item-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> </template> </div></div><div class="field"> <label class="label field-label" for="build">Build tool</label> <div class="combobox" x-data="slCombobox" data-result="1 result" data-results="{0} results"> <select class="combobox-select" id="build" name="build"> <option value="" hidden data-placeholder="true" selected>Select a build tool</option> <option class="combobox-item" value="maven">Maven</option> <option class="combobox-item" value="ant" disabled>Ant</option> <option class="combobox-item" value="gradle">Gradle</option> </select> <svg xmlns="http://www.w3.org/2000/svg" class="icon combobox-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> <template class="combobox-template"> <div class="combobox-control"> <input class="combobox-input" type="text" role="combobox" autocomplete="off" autocapitalize="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" placeholder="Select a build tool"> <button type="button" class="combobox-trigger" tabindex="-1" aria-expanded="false" aria-label="Show options"> <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> </button> </div> <div class="combobox-content" popover="manual"> <div class="combobox-list" role="listbox"></div> <div class="combobox-empty">No items found.</div> </div> <div class="combobox-status" role="status"></div> <svg xmlns="http://www.w3.org/2000/svg" class="icon combobox-item-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> </template> </div></div>Keyboard
Section titled “Keyboard”The focus stays in the input; the highlighted item is announced through aria-activedescendant, as the
ARIA combobox pattern
describes.
| Key | Does |
|---|---|
| Typing | Opens the list and filters it |
| Arrow down, arrow up | Opens the list on the chosen item; next, previous item |
| Alt + arrow down | Opens the list without highlighting |
| Page down, page up | Ten items further, back |
| Enter | Chooses the highlighted item; in a multiple combobox, adds or removes it and keeps the list open |
| Alt + arrow up | Chooses the highlighted item and closes the list |
| Escape | Closes the list, putting back the text of the choice |
| Tab | Closes the list without choosing and moves on |
| Home, End | Move the caret, as in any text field |
| Backspace (multiple, empty input) | Removes the last chip |
| Arrow left (multiple, at the start of the input) | Moves to the chips’ remove buttons: arrow left and right between them, Backspace or Delete removes one |
The number of matches is announced once typing pauses (“3 results”; the texts are messages, sl.combobox.*, see
Translating). The clear and chevron buttons stay out of the tab order: each
has a key. The item under the pointer is highlighted.
With htmx
Section titled “With htmx”- A response that renders the form again with errors keeps the chosen values (
th:fieldmarks them). - A swap that replaces an open combobox leaves nothing open behind.
- Back restores the combobox as htmx saved it; it builds its input and list again from the restored select.
- A combobox can trigger a request itself:
hx-get="/cities" hx-trigger="change" hx-target="#city-field". Options swapped into the select are picked up by the list.
Forced colours
Section titled “Forced colours”The input and the list keep a visible border, the highlighted item an outline and the chips a border, when the
system’s colours replace the page’s (Windows High Contrast). A disabled combobox turns GrayText instead of fading.
Left out
Section titled “Left out”These are left out on purpose:
- The popup layout, with a button as the trigger and the input inside the list, and addons in the input.
- Free text: the value is always one of the items.
- Chips that cannot be removed: a chip always has its remove button, and shows the item’s text.
- Placing the list elsewhere: the list opens below the input, or above it when there is no room.
Without JavaScript
Section titled “Without JavaScript”The server renders a real select, drawn like the native select: a single combobox is a select
with a chevron, a multiple one a list box (Ctrl- or Cmd-click chooses several). Both bind, submit and validate as any
select. With Alpine, slCombobox puts its input and list in the select’s place.
Attributes
Section titled “Attributes”sl:combobox
Section titled “sl:combobox”| Attribute | Values | Default | Description |
|---|---|---|---|
placeholder | string | — | The input's text while nothing is chosen. For a single value also the text of a first option with an empty value, hidden from the list: a required combobox with it chosen is invalid. |
multiple | boolean | — | Choose any number of options, shown as chips before the input. The select is a select multiple, so th:field binds a collection. |
show-clear | boolean | — | A button that clears the choice, in place of the chevron while there is one. |
show-trigger | boolean | true | The chevron button that opens and closes the list. A multiple combobox has none. |
auto-highlight | boolean | — | Highlight the first match while typing, so Enter chooses it. |
empty | string | — | The text shown when no option matches. By default "No items found." |
search-url | string | — | Ask the server for the options as you type: a GET to this URL with the text as q, answered with the HTML of sl:combobox-items (and groups and separators), which replace the options that are not chosen. Needs JavaScript; without it, only the options in the page can be chosen. |
Every prop can also be set from an expression: th:placeholder="${...}". Any other attribute passes through to the rendered element, and class is appended.
sl:combobox-item
Section titled “sl:combobox-item”sl:combobox-item declares no props; every attribute passes through.
sl:combobox-group
Section titled “sl:combobox-group”| Attribute | Values | Default | Description |
|---|---|---|---|
label | string | required | The group's heading. |
Every prop can also be set from an expression: th:label="${...}". Any other attribute passes through to the rendered element, and class is appended.
sl:combobox-separator
Section titled “sl:combobox-separator”sl:combobox-separator declares no props; every attribute passes through.
JavaScript
Section titled “JavaScript”A small Alpine component, slCombobox, sits on the wrapper: <div class="combobox" x-data="slCombobox">.
- It hides the
<select>(it stays in the form, and still submits, resets and validates) and adds what the wrapper’s<template>holds after it: a<div class="combobox-control">with an<input role="combobox" class="combobox-input">(and the chips, the clear and the chevron buttons), and a<div class="combobox-content" popover="manual">with the<div role="listbox" class="combobox-list">. The input takes the select’s label (its<label>s,aria-labelledbyoraria-label),aria-describedby,aria-invalid,requiredanddisabled, and follows them when they change. Focus that reaches the hidden select moves on to the input. - The list is a popover in the top layer, so it shows above dialogs and sheets, placed with anchor positioning (with a stand-in for browsers without it). Escape in the input closes the list, not the dialog around it.
- It works with Alpine’s CSP build and a strict Content Security Policy: no inline styles, no
eval. A search request is afetchto the same origin (connect-src 'self'). - Keep your own
x-dataoff<sl:combobox>:classis the only attribute that reaches the wrapper. Listen forchangeon the select instead.