<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 - Use
data-nofilter
to prevent filtering - Use
data-*
attributes to translate screen reader announcements - Use
popover
attribute to activate Popover API - Want to show suggestions from a data source? See u-combobox →
- 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" data-sr-singular="%d flavor" data-sr-plural="%d flavours"> <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 @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>
- Screen reader attributes required to meet WCAG §4.1.3:
data-sr-singular="%d hit"
announces single hitdata-sr-plural="%d hits"
announces multiple hits- Note: If
<u-datalist>
has no options, visible text will be announced instead (i.e. No results)
- DOM interface:
HTMLDataListElement
HTMLDataListElement.options
returnsHTMLCollectionOf<HTMLOptionElement>
. However, note that there is inconsistency between Firefox and other browsers, regarding whetherdisabled
options are included or not. To ensure access to all options, consider using.children
instead.
<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 option clicks by binding an input
listener to <input>
,
and use the utility import { isDatalistClick } from '@u-elements/u-datalist'
:
<input type="text" list="my-list" />
<u-datalist id="my-list">
<u-option>Option 1</u-option>
<u-option>Option 2</u-option>
</u-datalist>
<script type="module">
import { isDatalistClick } from '@u-elements/u-datalist';
const input = document.querySelector('input')
input.addEventListener('input', (event) => {
if (isDatalistClick(event)) {
// Event is triggered by user selecting an option in datalist
} else {
// Event is triggered by user typing in input
}
})
</script>
import { isDatalistClick } from '@u-elements/u-datalist';
const MyComponent = () => {
const handleChange = (event) => {
if (isDatalistClick(event.nativeEvent)) {
// Event is triggered by user selecting an option in datalist
} else {
// Event is triggered by user typing in input
}
};
return (
<input type="text" list="my-list" onChange={handleChange} />
<u-datalist id="my-list">
<u-option>Option 1</u-option>
<u-option>Option 2</u-option>
</u-datalist>
);
}
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 */
}
Example: Styling
<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" data-sr-singular="%d flavor" data-sr-plural="%d flavours"> <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: Popover
Using Popover API allows datalist to automatically render on top layer and cross boundries of scroll containers. Keep in mind that you're responsible for styling and positioning the datalist, just as you would with any other element using popover
.
<label for="my-popover-input"> Choose flavor of ice cream <input type="text" id="my-popover-input" list="my-popover" /> </label> <u-datalist popover id="my-popover" data-sr-singular="%d flavor" data-sr-plural="%d flavours"> <u-option>Coconut</u-option> <u-option>Strawberries</u-option> <u-option>Chocolate</u-option> <u-option>Vanilla</u-option> </u-datalist>
Example: Dynamic
<label for="my-dynamic-input">Choose your email</label> <input type="text" id="my-dynamic-input" list="my-dynamic-list" placeholder="Type email..." /> <u-datalist id="my-dynamic-list" data-nofilter data-sr-singular="%d suggestion" data-sr-plural="%d suggestions"> </u-datalist> <script type="module"> const input = document.getElementById('my-dynamic-input'); input.addEventListener('input', (event) => { const value = input.value.split("@")[0].trim(); const values = [ `${value}@live.com`, `${value}@icloud.com`, `${value}@hotmail.com`, `${value}@gmail.com`, ]; input.list.textContent = ''; if (value) input.list?.append(...values.map((text) => Object.assign(document.createElement("u-option"), { text })) ); }); </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
Changelog
- 1.0.4: Enhance Android compatibility
- 1.0.3: Enhance
JSDOM
compatibility by not assigningview
inFocusEvent
- 1.0.2: Prevent error if removing
<input>
duringinput
event - 1.0.1: Restore call to
togglePopover
- 1.0.0:
- Removed support for
isDatalistClick
,syncDatalistState
andgetDatalistValue
- Added support for
data-nofilter
- Fixed bug where VoiceOver + Safari announced incorrect amount of list items
- Improved support for changes in
disabled
,hidden
,label
andvalue
attributes - Improved
popover
support
- Removed support for