Key features
- Single and multiple selection support
- Accessibility: Use of ARIA attributes, custom screen reader announcements, and tested with assistive technologies.
- Progressive enhancement: Automatic source building through specifying a <select> as the element, or an element with child checkboxes.
- Extensible source options: Array of Strings or Objects, Function (or Promise), or an endpoint.
- Compatibility: Broad browser and device support (IE9+).
- Starting values: Automatic selection based on starting values, including for checkboxes, select options, and for async handling.
- Small script size: 10 kB gzipped.
Grab from NPM and use in a module system, or grab the code from unpkg:
npm install aria-autocomplete
<script src="https://unpkg.com/aria-autocomplete"></script>
And call with an element and options:
AriaAutocomplete(element, options);
Examples
1. Array as Source, using default options
When the source is an Array, it can either be an Array of Strings:
AriaAutocomplete(document.getElementById('input'), {
source: [
'Afghanistan',
'Albania',
'Algeria',
...etc
]
});
Or you can use an Array of Objects with value
and / or label
properties (if only a value
or label
is provided, that property will be used for both):
AriaAutocomplete(document.getElementById('input'), {
source: [
{
value: 'AFG',
label: 'Afghanistan'
},
{
value: 'ALB',
label: 'Albania'
},
...etc
]
});
The autocomplete will search by the label
property by default, and set the value
in the original element (if an <input />
was used).
You can add an Array of Strings to the alsoSearchIn
option to indicate other properties to filter by as well as the label
.
If your objects don't have value
and / or label
properties, and you're unable to transform them ahead of use, there is also a sourceMapping
option:
AriaAutocomplete(document.getElementById('input'), {
source: [
{
code3: 'AFG',
name:' Afghanistan'
},
{
code3: 'ALB',
name: 'Albania'
},
...etc
],
sourceMapping: { 'value': 'code3', 'label': 'name' },
alsoSearchIn: ['value']
});
2. Progressive Enhancement, using Element(s) as Source
(With result creation, multi-select, autogrow, results limit, and show all control)
AriaAutocomplete(document.querySelector('select'), {
placeholder: 'Type to refine',
deleteOnBackspace: true,
showAllControl: true,
autoGrow: true,
maxResults: 50,
create: true,
minLength: 0,
maxItems: 5
});
If the source
option is falsy, or is an empty Array, the autocomplete will check to see if the element used is a <select>
, or if there are any checkboxes inside the element, and will build up a source
Array from what's available.
This example uses a <select>
element; so when an option in the autocomplete is selected (or removed), the corresponding <option>
element will also be selected (or deselected) as well.
Any pre-selected <option>
or checkbox elements are respected, and the multiple
option will also be set to true
automatically if the <select>
has that attribute set. If checkboxes are used, the multiple
option will always be set to true
.
When using the create
option along with progressive enhancement, the created selections will be added to the original <select>
element or checkbox list. So they will always remain as available options (both internally, and in the DOM) even after being de-selected.
3. Function as Source
(With delete all control and multi-select, but without autogrow)
If you use a Function for the source
with your own filtering logic, it will be passed the search query, and a render Function, which expects an Array (using either of the formats mentioned above).
AriaAutocomplete(document.getElementById('input'), {
maxItems: 4,
maxResults: 20,
multiple: true,
deleteAllControl: true,
placeholder: 'Type to search',
source: (query: string, render: (toRender: any[]) => void, isFirstCall: boolean) => {
const toRender: any[] = [];
// build up your Array here, then render...
render(toRender);
},
onItemRender: (itemData: any) => {
return `${itemData.label} (${itemData.value})`;
}
});
The source
function can also be a Promise
which resolves with the items to render, instead of having to use the provided second argument callback.
If the autocomplete is initialised on an <input />
with a starting value, the source
function will also be called immediately using that value. In this case, the render function expects an Array of possible entries to check that starting value against, to determine what the starting label (or multi-select elements) should be.
The onItemRender
callback is also being used here to display the code next to the country name in the results.
The initialised input's starting value is "GLP,ZWE" - the country codes for Guadeloupe and Zimbabwe respectively.
4. String (endpoint) as Source
If you want to send the query to an endpoint, you can do that too.
AriaAutocomplete(document.getElementById('input'), {
source: 'some-url-here/endpoint',
onAsyncPrep: (url: string) => url,
onAsyncSuccess: (query: string, xhr: XMLHttpRequest, isFirstCall: boolean) => {
return JSON.parse(xhr.responseText);
},
asyncMaxResultsParam: 'limit',
asyncQueryParam: 'q',
maxResults: 25
});
As with a Function as the source
, if the autocomplete is initialised on an input with a starting value, the endpoint will be called immediately.
The current query and maxResults
will be added to the url using the asyncQueryParam
and asyncMaxResultsParam
options respectively.
E.g. https://some-url-here/endpoint?q=norway&limit=20.
You can also use the onAsyncPrep
callback to modify the URL.
This example has no filtering in the request, as it's just requesting a JSON file, so it's doing some basic filtering in the onAsyncSuccess
callback. The initialised input's starting value is "CAN" (the code3 property for Canada).
5. Array as Source, with Result Creation and Multi-Select
This example simulates a tagging input. It has a small source
array, but with result creation enabled, and includes starting values not included in the source
.
const autocomplete = AriaAutocomplete(document.getElementById('input'), {
source: ['Sophie', 'John', ...more],
onItemRender: ({ label }) => {
if (!autocomplete.options.source.includes(label)) {
return `Add ${label}...`;
}
return label;
},
deleteOnBackspace: true,
autoGrow: true,
multiple: true,
minLength: 0,
create: true
});
All component options that accept a Function will have their context (this
) set to include the full autocomplete API (assuming you use an ordinary function expression instead of arrow functions). The only exception to this is the Function and Endpoint starting cases mentioned above, where the context is not manually set. So you can check for the API's existence, or check the context against the window object, to check if it's the starting call (or use the provided final param in the relevant callbacks).
Options
The full list of available options is as follows:
Core options
- name:
string
- Give the autocomplete a name so that (in single-select mode) its value will be included in form submissions.
(Instead of using this option, I would advise initialising the autocomplete on an existing input that will be submitted; this approach is compatible with the control in multiple mode) - source:
string[] | any[] | string | Function | Promise
- string for async endpoint, array of strings, array of objects with value and label, or function
- sourceMapping:
Object
- an object detailing the properties to use for the label and value when using an Array of Objects as the source - see Array examples above.
- alsoSearchIn:
string[]
- Additional properties to use when searching for a match
- create:
boolean | Function
-
If no exact match is found, create an entry in the results list for the current search text.
Can use a function that receives the search term, and returns a string or an object (like a normal static source entry).
Default:false
- delay:
number
- input delay before running a search
Default:100
- minLength:
number
- Minimum number of characters to run a search (includes spaces)
Default:1
- maxResults:
number
- Maximum number of results to render
Default:9999
- showAllControl:
boolean
- Render a button that triggers showing all options
Default:false
- confirmOnBlur:
boolean | Function
-
Confirm current selection (from using arrow keys) when blurring off of the control. Will also check for a label match if there is no current selection.
Can use a function which receives the search term and results, and returns a string to be used to compare against the result labels.
Default:true
- multiple:
boolean
- Allow multiple items to be selected
Default:false
- autoGrow:
boolean
- Set input width to match its content
Default:false
- maxItems:
number
- Maximum number of items that can be selected in multiple mode
Default:9999
- multipleSeparator:
string
- In multiple mode, if the original element was an input, the character that separates the values
Default:`,`
- deleteOnBackspace:
boolean
- If input is empty and in multiple mode, delete last selected item on backspace
Default:false
- deleteAllControl:
boolean
- In multiple mode, if more than 1 item is selected, add a button at the beginning of the selected items as a shortcut to delete all
Default:false
- deleteAllText:
string
- Text to use in the
deleteAllControl
Default:`Delete all`
Async mode options
- asyncQueryParam:
string
- When source is a string, param to use when adding input value to it
Default:`q`
- asyncMaxResultsParam:
string
- When source is a string, param to use when adding maxResults option (+ current selected count in multiple mode) to it
Default:`limit`
Styling / rendering options
- placeholder:
string
- Placeholder text to show in generated input
Default:``
- noResultsText:
string
- Text to show (and announce to screen readers) if no results are found. If this is an empty string, the list will close instead
Default:`No results`
- cssNameSpace:
string
- The string to prepend to all main classes for BEM naming e.g. `aria-autocomplete__input`
(Some other generic class-names are used alongside these as well, such as`disabled`
,`focused`
,`hidden`
, etc.)
Default:`aria-autocomplete`
- listClassName:
string
- Custom class name to add to the generated list element
Default:``
- inputClassName:
string
- Custom class name to add to the generated input element
Default:``
- wrapperClassName:
string
- Custom class name to add to the generated component wrapper
Default:``
Screen reader enhancements
- srDelay:
number
-
Set the delay in milliseconds before screen reader announcements are made.
Note: if this is too short, some default announcements may interrupt it.
Default:5000
- srAutoClear:
boolean | number
- Automatically clear the screen reader announcement element after the specified delay. Number is in milliseconds.
Default:10000
- srDeleteText:
string
- In multi mode, screen reader text used for element deletion - prepended to label
Default:`delete`
- srDeletedText:
string
- Screen reader text announced after deletion - appended to label
Default:`deleted`
- srShowAllText:
string
- Screen reader text for the show all button
Default:`Show all`
- srSelectedText:
string
- Screen reader text announced after selection - appended to label
Default:`selected`
- srListLabelText:
string
- Screen reader explainer added to the list element via aria-label attribute
Default:`Search suggestions`
- srAssistiveText:
string
- Screen reader description used for main input when empty
Default:`When results are available use up and down arrows to review and enter to select. Touch device users, explore by touch or with swipe gestures.`
- srAssistiveTextAutoClear:
boolean
-
Automatically remove the srAssistiveText once user input is detected, to reduce screen reader verbosity.
The text is re-associated with the generated input if its value is emptied.
Default:5000
- srResultsText:
Function
- Screen reader announcement after results are rendered
Default:length => `${length} ${length === 1 ? 'result' : 'results'} available.`
Callbacks
- onSearch:
Function
-
Before search is performed - can be used to affect search value by returning a new one
Default:(query: string) => query
- onAsyncPrep:
Function
-
Callback before async call is made - receives the URL. Can be used to format the endpoint URL by returning a String, and for changes to the XHR object.
Note: this is before theonload
andonerror
functions are attached, and before theopen
method is called Default:(url: string, xhr: XMLHttpRequest, isFirstCall: boolean) => url
- onAsyncBeforeSend:
Function
-
Callback before async call is sent - receives the XHR object. Can be used for final changes to the XHR object, such as adding auth headers
Default:(query: string, xhr: XMLHttpRequest, isFirstCall: boolean) => {}
- onAsyncSuccess:
Function
-
After async call succeeds, but before the results render - is passed the value and the XHR Object. Can be used to format the results by returning an Array
Default:(query: string, xhr: XMLHttpRequest, isFirstCall: boolean) => xhr.responseText
- onAsyncComplete:
Function
-
After async call completes successfully, and after the results have rendered.
Default:(query: string, xhr: XMLHttpRequest, isFirstCall: boolean) => {}
- onAsyncError:
Function
-
If async call fails - is passed the value and the XHR Object.
Default:(query, xhr: XMLHttpRequest, isFirstCall: boolean) => {}
- onItemRender:
Function
-
Called for each item rendered in the list - Can be used to format the
<li>
content by returning a String
Default:(itemData: any) => itemData.label
- onResponse:
Function
-
Prior to rendering - can be used to format the results by returning an Array
Default:(optionsToRender: any[]) => optionsToRender
- onConfirm:
Function
-
After an autocomplete selection is made
Default:(selection: any) => {}
- onDelete:
Function
-
After an autocomplete selection is deleted (programmatically in single-select mode, or by user action in multi-select mode)
Default:(deletion: any) => {}
- onChange:
Function
-
When the selected item(s) changes
Default:(selectedItems: any[]) => {}
- onFocus:
Function
-
When the overall component receives focus
Default:(componentWrapper: HTMLDivElement) => {}
- onBlur:
Function
-
When the overall component loses focus
Default:(componentWrapper: HTMLDivElement) => {}
- onReady:
Function
-
When main script processing and initial rendering has finished
Default:(componentWrapper: HTMLDivElement) => {}
- onClose:
Function
-
When list area closes
Default:(listElement: HTMLUListElement) => {}
- onOpen:
Function
-
When list area opens
Default:(listElement: HTMLUListElement) => {}