Aria Autocomplete
Accessible, extensible, plain JavaScript autocomplete with multi-select.
Try out the examples.
Key design goals and features:
- multiple selection
- extensible source options: Array of Strings, Array of Objects, a Function, or an endpoint String
- progressive enhancement: Automatic source building through specifying a
<select>
as the element, or an element with child checkboxes. - accessibility: Use of ARIA attributes, custom screen reader announcements, and testing with assistive technologies
- compatibility: Broad browser and device support (IE9+)
- starting values: Automatic selection based on starting values, including for checkboxes,
select
options, and for async handling.
Built from the ground up for the accessibility, performance, and functionality combination that I couldn't find in any other autocomplete plugins.
The more general aim here was to build upon the brilliant accessibility of GOV.UK's accessible-autocomplete, but with more functionality, a smaller file size, and (in my testing) better performance (and without using Preact).
Installation / usage
NPM and a module system
First install it
npm install aria-autocomplete
Then import it, and call it on an element (ideally a text <input />
, but not necessarily...) with a source for your autocomplete options.
import AriaAutocomplete from 'aria-autocomplete';
AriaAutocomplete(document.getElementById('some-element'), {
source: ArrayOrStringOrFunction,
});
At its core, the autocomplete requires only an element and a source
. When the element is an input, its value will be set using the user's selection(s). If a source
option isn't provided (is falsy, or an empty Array), and the element is either a <select>
, or has child checkboxes, those will be used to build up the source
.
AriaAutocomplete(document.getElementById('some-input'), {
source: ['Afghanistan', 'Albania', 'Algeria', ...more],
});
const select = document.getElementById('some-select');
AriaAutocomplete(select);
const div = document.getElementById('some-div-with-child-checkboxes');
AriaAutocomplete(div);
Plain JavaScript module
You can grab the minified JS from the dist
directory, or straight from unpkg:
<script src="https://unpkg.com/aria-autocomplete" type="text/javascript"></script>
Styling Aria Autocomplete
I would encourage you to style it yourself to match your own site or application's design. An example stylesheet is included in the dist
directory however which you can copy into your project and import into the browser.
Performance
I wrote this from the ground up largely because I needed an autocomplete with better performance than others I'd tried. I've optimised the JavaScript where I can, but in some browsers the list rendering will still be a hit to performance. In my testing, modern browsers can render huge lists (1000+ items) just fine (on my laptop, averaging 40ms in Chrome, and under 20ms in Firefox).
As we all know however, Internet Explorer sucks. If you need to support Internet Explorer, I suggest using a sensible combination for the delay
, maxResults
, and possibly minLength
options, to prevent the browser from freezing as your users type, and to reduce the rendering impact. Testing on my laptop, the list rendering in IE11 would take on average: 55ms for 250 items, 300ms for 650 items, and over 600ms for 1000 items.
Options
The full list of options, and their defaults:
{
name: string;
source: string[] | any[] | string | Function | Promise<any[]>;
sourceMapping: any = {};
alsoSearchIn: string[] = [];
create: boolean | ((value: string) => string | object) = false;
delay: number = 100;
minLength: number = 1;
maxResults: number = 9999;
showAllControl: boolean = false;
confirmOnBlur: boolean = true;
multiple: boolean = false;
autoGrow: boolean = false;
maxItems: number = 9999;
multipleSeparator: string = `,`;
deleteOnBackspace: boolean = false;
deleteAllControl: boolean = false;
deleteAllText: string = `Delete all`;
asyncQueryParam: string = `q`;
asyncMaxResultsParam: string = `limit`;
placeholder: string;
noResultsText: string = `No results`;
cssNameSpace: string = `aria-autocomplete`;
listClassName: string;
inputClassName: string;
wrapperClassName: string;
srAutoClear: boolean | number = 5000;
srDeleteText: string = `delete`;
srDeletedText: string = `deleted`;
srShowAllText: string = `Show all`;
srSelectedText: string = `selected`;
srListLabelText: string = `Search suggestions`;
srAssistiveText: string =
`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.`;
srResultsText: (length: number) => string | void = (length: number) =>
`${length} ${length === 1 ? 'result' : 'results'} available.`;
onSearch: (value: string) => string | void;
onAsyncPrep: (url: string, xhr: XMLHttpRequest, isFirstCall: boolean) => string | void;
onAsyncBeforeSend: (query: string, xhr: XMLHttpRequest, isFirstCall: boolean) => void;
onAsyncSuccess: (query: string, xhr: XMLHttpRequest, isFirstCall: boolean) => any[] | void;
onAsyncComplete: (query: string, xhr: XMLHttpRequest, isFirstCall: boolean) => void;
onAsyncError: (query: string, xhr: XMLHttpRequest, isFirstCall: boolean) => any[] | void;
onResponse: (options: any[]) => any[] | void;
onItemRender: (sourceEntry: any) => string | void;
onConfirm: (selected: any) => void;
onDelete: (deleted: any) => void;
onChange: (selected: any[]) => void;
onFocus: (wrapper: HTMLDivElement) => void;
onBlur: (wrapper: HTMLDivElement) => void;
onReady: (wrapper: HTMLDivElement) => void;
onClose: (list: HTMLUListElement) => void;
onOpen: (list: HTMLUListElement) => void;
}
Calling AriaAutocomplete(element, options);
returns the API object, which can also be accessed on the element via element.ariaAutocomplete
.