<u-datalist>
<u-datalist>
lets you suggest values to a connected <input>
. You can use it to make things like comboboxes, autosuggest, autocomplete, live search results, etc.
Quick intro:
- Use
<u-option>
as direct child elements - these will show the suggestions while typing - Use matching
id
on<u-datalist>
andlist
attribute on<input>
to connect - Want to show suggestions from a data source? See example: API →
- MDN Web Docs: <datalist> (HTMLDatalistElement) / <option> (HTMLOptionElement)
Example
<label for="my-input"> Choose flavor of ice cream </label> <input id="my-input" list="my-list" /> <u-datalist id="my-list"> <u-option>Coconut</u-option> <u-option>Strawberries</u-option> <u-option>Chocolate</u-option> <u-option>Vanilla</u-option> <u-option>Licorice</u-option> <u-option>Pistachios</u-option> <u-option>Mango</u-option> <u-option>Hazelnut</u-option> </u-datalist> <style> /* Styling just for example: */ u-option[selected] { font-weight: bold } </style>
Install
npm add -S @u-elements/u-datalist
pnpm add -S @u-elements/u-datalist
yarn add -S @u-elements/u-datalist
bun add -S @u-elements/u-datalist
<script type="module" src="https://unpkg.com/@u-elements/u-datalist@latest/dist/u-datalist.js"></script>
Attributes and props
<u-datalist>
- Attributes: all global HTML attributes such as
id
,class
,data-
id
must be identical to value oflist
attribute on associated<input>
- DOM interface:
HTMLDataListElement
HTMLDataListElement.options
returnsHTMLCollectionOf<HTMLOptionElement>
<u-option>
- Attributes: all global HTML attributes such as
id
,class
,data-
disabled
disables and hides the element if present. Note: Settingdisabled="false"
will not work as intended, asdisabled
is a boolean attribute you should provide or remove entirely.label
label sets text indicating the meaning of the option. Iflabel
isn't defined, its value is that of the element text content.selected
sets option selected state. Note: Settingselected="false"
will not work as intended, asselected
is a boolean attribute you should provide or remove entirely.value
represents the value to be filled into associated<input>
. Ifvalue
isn't defined, the value is taken from the text content of the option element.
- DOM interface:
HTMLOptionElement
HTMLOptionElement.defaultSelected
returnstrue
orfalse
indicating stateHTMLOptionElement.disabled
returnstrue
offalse
reflecting thedisabled
attributeHTMLOptionElement.form
returns parentHTMLFormElement
HTMLOptionElement.index
returns anumber
representing the position/index within the list of optionsHTMLOptionElement.label
sets or getsstring
of optionlabel
HTMLOptionElement.selected
sets or getstrue
orfalse
indicating stateHTMLOptionElement.text
sets og getsstring
of option text contentHTMLOptionElement.value
sets og getsstring
of optionvalue
Events
While <u-datalist>
support all events, native datalist does not as it is rendered as part of the browser UI. Therefore, it's recommended to avoid binding events to <u-datalist>
or <u-option>
if you want to ensure native compatibility and future seamless opt-out.
Instead, you can detect the selection of an option element by
binding an input
listener to the <input>
element and check for a falsy event.inputType
:
<input type="text" list="my-list" />
<datalist id="my-list">
<option>Option 1</option>
<option>Option 2</option>
</datalist>
<script>
const input = document.querySelector('input')
input.addEventListener('input', (event) => {
if (!event.inputType) {
// Event is triggered by user selecting an option in datalist
} else {
// Event is triggered by user typing in input
}
})
</script>
Styling
While <u-datalist>
and <u-option>
are styleable, native datalist and option elements are currently not. However, there is a possibility that the native elements may support styling in the future.
Styling with the display property
<u-datalist>
and <u-option>
are both rendered as display: block
when visible, and are hidden by the hidden
attribute. Styling with a display
property will override the hidden
attribute, thus disabling datalist show/hide and option filtering, unless you wrap your styling in a :not([hidden])
selector:
u-datalist:not([hidden]) {
display: flex;
/* Use :not([hidden]) if you wish to change u-datalist display
* without disabling open/close */
}
u-option:not([hidden]) {
display: flex;
/* Use :not([hidden]) if you wish to change u-option display
* without disabling filtering */
}
Styling option focus and selected state
<u-option>
receive real focus on keyboard navigation, and a selected
attribute on selection, which both can be utilized for styling:
u-option:focus {
/* Focused option styling here */
}
u-option:not(:focus) {
/* Un-focused option styling here */
}
u-option[selected] {
/* Selected option styling here */
}
u-option:not([selected]) {
/* Un-selected option styling here */
}
Styling example: Datalist position and animation
<style> .my-input, .my-list { background: #fff; border-radius: .25em; border: 2px solid #090C33; box-sizing: border-box; color: #090C33; font: inherit; padding: .5em; width: 13em; transition: .2s; /* Animate */ } .my-input { background: #fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%23000' stroke-width='2' d='m3 8 9 9 9-9'/%3E%3C/svg%3E") center right/2rem 1rem no-repeat; } .my-input[aria-expanded="true"] { background-color: #f9ffd7; border-bottom-left-radius: 0; border-bottom-right-radius: 0; } .my-input:focus-visible, .my-list u-option:focus { box-shadow: 0 0 0 1px #fff,0 0 0 3px #6325e7,0 0 0 4px #fff; outline: none; } .my-list { border-top-left-radius: 0; border-top-right-radius: 0; box-shadow: 0 .3em 1em #090C3333; display: block; /* Overwrites hidden attribute */ margin-top: -2px; position: absolute; } .my-list[hidden] { opacity: 0; translate: 0 -.5em; visibility: hidden; } .my-list u-option { border-radius: .1em; padding: .5em; transition: .2s; } .my-list u-option:focus { background-color: #EBF0FA; } .my-list u-option[selected] { font-weight: bold; text-decoration: underline; } </style> <label for="my-styling-input"> Choose flavor of ice cream <input type="text" id="my-styling-input" class="my-input" list="my-styling" /> </label> <u-datalist class="my-list" id="my-styling"> <u-option>Coconut</u-option> <u-option>Strawberries</u-option> <u-option>Chocolate</u-option> <u-option>Vanilla</u-option> </u-datalist> <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus tristique tellus massa, eget sollicitudin arcu luctus vel. Cras non purus accumsan, ultricies mi ut, blandit magna. </p>
Example: API
<label for="my-api-input"> Choose country </label> <input type="text" id="my-api-input" list="my-api-list" /> <u-datalist id="my-api-list"> Type to search for countries... </u-datalist> <script> let debounceTimer; // Debounce so we do not spam API const input = document.getElementById('my-api-input'); const list = input.list; const xhr = new XMLHttpRequest(); // Easy to abort // Same handler every time xhr.onload = () => { try { const data = JSON.parse(xhr.responseText); const options = data.map(({ name }, index) => { const option = document.createElement('u-option'); option.text = name; option.value = `${index}: ${input.value}`; // Prevent filtering by matching value and input return option; }); list.replaceChildren(...options); } catch (err) { list.textContent = 'No results'; } }; input.addEventListener('input', (event) => { if (!event.inputType) { // User clicked u-option, lets get option.text const index = Number(input.value.split(':')[0]) const option = list.options[index]; input.value = option.text; } else if (!input.value) { list.textContent = 'Type to search for countries...'; } else { // User is typing const value = encodeURIComponent(event.target.value.trim()); list.textContent = 'Loading...'; xhr.abort(); clearTimeout(debounceTimer); debounceTimer = setTimeout(() => { xhr.open('GET', `https://restcountries.com/v2/name/${value}?fields=name`, true); xhr.send(); }, 300); } }); </script>
Example: Dynamic
<label for="my-dynamic-input"> Choose your email </label> <input type="text" id="my-dynamic-input" list="my-dynamic-list" /> <u-datalist id="my-dynamic-list"> Type to choose email... </u-datalist> <script> const input = document.getElementById('my-dynamic-input'); input.addEventListener('input', (event) => { if (!event.inputType) return; // User clicked u-option const value = input.value.split('@')[0] const values = [ `${value}@live.com`, `${value}@icloud.com`, `${value}@hotmail.com`, `${value}@gmail.com` ]; if (!value) input.list.textContent = 'Type to choose email...'; else input.list.replaceChildren(...values.map((text) => { const option = document.createElement('u-option'); option.text = text; return option; })); }); </script>
Example: Custom filter
<label for="my-filter-input"> Custom filter </label> <input type="text" id="my-filter-input" list="my-filter-list" /> <u-datalist id="my-filter-list"> <u-option>u-datalist</u-option> <u-option>u-details</u-option> <u-option>u-dialog</u-option> <u-option>u-progress</u-option> <u-option>u-select</u-option> <u-option>u-tabs</u-option> <u-option>u-tags</u-option> </u-datalist> <script> const input = document.getElementById('my-filter-input'); // Any custom filtering logic here: const isMatch = (needle, haystack) => haystack.toLowerCase().includes( needle.toLowerCase().trim() ); // Achieve custom filter on native datalist input.addEventListener("input", (event) => { if (!event.inputType) return; // User clicked u-option Array.from(input.list.options, (option) => { let text = option.getAttribute("data-text"); let value = option.getAttribute("data-value"); if (!text) text = option.dataset.text = option.text; if (!value) value = option.dataset.value = option.value; option.text = option.value = ""; if (isMatch(input.value, text)) { option.text = text; option.value = value; } }); }); </script>
Example: Link
<label for="my-link-input"> Open u-element documentation </label> <input type="text" id="my-link-input" list="my-link-list" /> <u-datalist id="my-link-list"> <u-option value="https://u-elements.github.io/u-elements/elements/u-datalist">u-datalist</u-option> <u-option value="https://u-elements.github.io/u-elements/elements/u-details">u-details</u-option> <u-option value="https://u-elements.github.io/u-elements/elements/u-dialog">u-dialog</u-option> <u-option value="https://u-elements.github.io/u-elements/elements/u-progress">u-progress</u-option> <u-option value="https://u-elements.github.io/u-elements/elements/u-select">u-select</u-option> <u-option value="https://u-elements.github.io/u-elements/elements/u-tabs">u-tabs</u-option> <u-option value="https://u-elements.github.io/u-elements/elements/u-tags">u-tags</u-option> </u-datalist> <script> const input = document.getElementById('my-link-input'); input.addEventListener('input', (event) => { if (!event.inputType) { // User clicked u-option window.location.href = input.value; input.value = ''; // Clear input } }); </script>
Accessibility
Screen reader | <datalist> | <u-datalist> |
---|---|---|
VoiceOver (Mac) + Chrome | ❌ Does not announce option count | ✅ |
VoiceOver (Mac) + Edge | ✅ | ✅ |
VoiceOver (Mac) + Firefox | ❌ Does not announce option count | ✅ |
VoiceOver (Mac) + Safari | ❌ Does not announce option count | ✅ |
VoiceOver (iOS) + Chrome | ❌ Does not announce options | ✅ |
VoiceOver (iOS) + Safari | ❌ Does not announce options | ✅ |
Jaws (PC) + Chrome | ✅ | ✅ |
Jaws (PC) + Edge | ✅ | ✅ |
Jaws (PC) + Firefox | ✅ | ✅ |
NVDA (PC) + Chrome | ✅ | ✅ |
NVDA (PC) + Edge | ✅ | ✅ |
NVDA (PC) + Firefox | ✅ | ✅ |
Narrator (PC) + Chrome | ✅ | ✅ |
Narrator (PC) + Edge | ✅ | ✅ |
Narrator (PC) + Firefox | ❌ Does not show options | ✅ |
TalkBack (Android) + Chrome | ✅ | ✅ |
TalkBack (Android) + Firefox | ❌ Does not show options | ✅ |
TalkBack (Android) + Samsung Internet | ✅ | ✅ |
Specifications
- DOM interface: HTMLDatalistElement
- HTML Standard: The <datalist> element
- DOM interface: HTMLOptionElement
- HTML Standard: The <option> element