Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

aria-autocomplete

mynamesleon3.9kGPL-3.0-or-later1.5.1TypeScript support: included

Accessible, extensible, JavaScript autocomplete with multi-select

autocomplete, typeahead, autosuggest, a11y, ARIA, WAI-ARIA, accessible, accessibility, dropdown, enhanced input, select, combobox, multiselect, multiple selection, component, plugin, widget

readme

Aria Autocomplete

npm version gzip size

Fast, accessible, extensible, plain JavaScript autocomplete with multi-select.

Try out the examples.

Key design goals and features:

  • support 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.
  • small: less than 11 kB gzipped

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

While this was written from the ground up to have better performance than other autocompletes I've tested, in much older browser the rendering of large lists will still be a hit to performance. In my testing, modern browsers can render even huge lists (1000+ items) just fine (on my laptop, averaging <40ms in Chrome, and <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 stuttering 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:

{
    /**
     * Give the autocomplete a name to be included in form submissions
     * (Instead of using this option, I would advise initialising the autocomplete on
     * an existing input that will be submitted, to also use any existing validation;
     * this approach is also compatible with the control in multiple mode)
     */
    name: string;

    /**
     * Specify source. See examples file for more specific usage.
     * @example ['Afghanistan', 'Albania', 'Algeria', ...more]
     * @example [{ label: 'Afghanistan', value: 'AFG' }, ...more]
     * @example 'https://some-endpoint.somewhere/available'
     * @example (query, render, isFirstCall) => render(arrayToUse)
     * @example (query) => async () => arrayToUse
     */
    source: string[] | any[] | string | Function | Promise<any[]>;

    /**
     * Properties to use for label and value when source is an Array of Objects
     */
    sourceMapping: any = {};

    /**
     * Additional properties to use when searching for a match.
     * `label` will always be used
     */
    alsoSearchIn: string[] = [];

    /**
     * If no exact match is found,
     * create an entry in the options list for the current search text
     */
    create: boolean | ((value: string) => string | object) = false;

    /**
     * Input delay after typing before running a search
     */
    delay: number = 100;

    /**
     * Minimum number of characters to run a search (includes spaces)
     */
    minLength: number = 1;

    /**
     * Maximum number of results to render. Also used in async endpoint URL
     */
    maxResults: number = 9999;

    /**
     * Render a control that triggers showing all options.
     * Runs a search with an empty query: '', and maxResults of 9999
     */
    showAllControl: boolean = false;

    /**
     * Confirm currently active selection when blurring off of the control.
     * If no active selection, will compare current input value against available labels.
     * Can also be a function that receives the search term and results, which can
     * return a string to be used in the comparison instead of the original search term.
     * Note: the comparison will be done with "cleaned" versions of the value and labels
     * (ignoring quotes, commas, colons, and hyphens, normalising "&" and "and",
     * and removing duplicate whitespace)
     */
    confirmOnBlur: boolean | ((value: string, options: any[]) => string | void) = true;

    /**
     * Allow multiple items to be selected
     */
    multiple: boolean = false;

    /**
     * Adjust input width to match its value. This will incur a performance hit
     */
    autoGrow: boolean = false;

    /**
     * Maximum number of items that can be selected in multiple mode
     */
    maxItems: number = 9999;

    /**
     * If initialised element is an input, and in multiple mode,
     * character that separates the selected values e.g. "GLP,ZWE"
     */
    multipleSeparator: string = `,`;

    /**
     * If input is empty and in multiple mode,
     * delete last selected item on backspace
     */
    deleteOnBackspace: boolean = false;

    /**
     * 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
     */
    deleteAllControl: boolean = false;

    /**
     * Text to use in the deleteAllControl
     */
    deleteAllText: string = `Delete all`;

    /**
     * In async mode, parameter to use when adding the input value to the
     * endpoint String. e.g. https://some-endpoint?q=norway&limit=9999
     */
    asyncQueryParam: string = `q`;

    /**
     * In async mode, parameter to use when adding results limit to the
     * endpoint String. e.g. https://some-endpoint?q=norway&limit=9999
     */
    asyncMaxResultsParam: string = `limit`;

    /**
     * Placeholder text to show in generated input
     */
    placeholder: string;

    /**
     * Text to show (and announce to screen readers) if no results found.
     * If empty, the list of options will remain hidden when there are no results
     */
    noResultsText: string = `No results`;

    /**
     * String to prepend to classes for BEM naming
     * e.g. aria-autocomplete__input
     */
    cssNameSpace: string = `aria-autocomplete`;

    /**
     * Custom class name to add to the options list holder
     */
    listClassName: string;

    /**
     * Custom class name to add to the generated input
     */
    inputClassName: string;

    /**
     * Custom class name to add to the component wrapper
     */
    wrapperClassName: string;

    /**
     * Set the delay (in milliseconds) before screen reader announcements are made.
     * Note: if this is too short, some default announcements may interrupt it,
     * particularly with screen readers that re-announce input values after a pause in typing.
     */
    srDelay: number = 1400;

    /**
     * Automatically clear the screen reader announcement element after the specified delay
     * Number is in milliseconds. If true, defaults to 10000.
     */
    srAutoClear: boolean | number = 10000;

    /**
     * Screen reader text used in multiple mode for element deletion.
     * Prepended to option label in aria-label attribute e.g. 'delete Canada'
     */
    srDeleteText: string = `delete`;

    /**
     * Screen reader text announced after deletion.
     * Apended to option label e.g. 'Canada deleted'
     */
    srDeletedText: string = `deleted`;

    /**
     * Value for aria-label attribute on the show all control
     */
    srShowAllText: string = `Show all`;

    /**
     * Screen reader text announced after confirming a selection.
     * Appended to option label e.g. 'Canada selected'
     */
    srSelectedText: string = `selected`;

    /**
     * Screen reader explainer added to the list element via aria-label attribute
     */
    srListLabelText: string = `Search suggestions`;

    /**
     * Screen reader description announced when the input receives focus.
     * Only announced when input is empty
     */
    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.`;

    /**
     * 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
     */
    srAssistiveTextAutoClear: boolean = true;

    /**
     * Screen reader announcement after results are rendered
     */
    srResultsText: (length: number) => string | void = (length: number) =>
        `${length} ${length === 1 ? 'result' : 'results'} available.`;

    /**
     * Callback before a search is performed - receives the input value.
     * Can be used to alter the search value by returning a String
     */
    onSearch: (value: string) => string | void;

    /**
     * Callback before async call is made - receives the URL, and the XHR object.
     * Can be used to format the endpoint URL by returning a String,
     * and for changes to the XHR object.
     * Note: this is before the onload and onerror functions are attached
     * and before the `open` method is called
     */
    onAsyncPrep: (url: string, xhr: XMLHttpRequest, isFirstCall: boolean) => string | void;

    /**
     * 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
     */
    onAsyncBeforeSend: (query: string, xhr: XMLHttpRequest, isFirstCall: boolean) => void;

    /**
     * Callback after async call succeeds, but before results render - receives the xhr object.
     * Can be used to format the results by returning an Array
     */
    onAsyncSuccess: (query: string, xhr: XMLHttpRequest, isFirstCall: boolean) => any[] | void;

    /**
     * Callback after async call completes successfully, and after the results have rendered.
     */
    onAsyncComplete: (query: string, xhr: XMLHttpRequest, isFirstCall: boolean) => void;

    /**
     * Callback if async call fails.
     */
    onAsyncError: (query: string, xhr: XMLHttpRequest, isFirstCall: boolean) => any[] | void;

    /**
     * Callback prior to rendering - receives the options that are going to render.
     * Can be used to format the results by returning an Array
     */
    onResponse: (options: any[]) => any[] | void;

    /**
     * Callback when rendering items in the list - receives the source option.
     * Can be used to format the <li> content by returning a String
     */
    onItemRender: (sourceEntry: any) => string | void;

    /**
     * Callback after selection is made - receives an object with the option details
     */
    onConfirm: (selected: any) => void;

    /**
     * Callback after an autocomplete selection is deleted.
     * Fires in single-select mode when selection is deleted automatically.
     * Fires in multi-select mode when selected is deleted by user action
     */
    onDelete: (deleted: any) => void;

    /**
     * Callback that fires when the selected item(s) changes
     */
    onChange: (selected: any[]) => void;

    /**
     * Callback when the overall component receives focus
     */
    onFocus: (wrapper: HTMLDivElement) => void;

    /**
     * Callback when the overall component loses focus
     */
    onBlur: (wrapper: HTMLDivElement) => void;

    /**
     * Callback when main script processing and initial rendering has finished
     */
    onReady: (wrapper: HTMLDivElement) => void;

    /**
     * Callback when list area closes - receives the list holder element
     */
    onClose: (list: HTMLUListElement) => void;

    /**
     * Callback when list area opens - receives the list holder element
     */
    onOpen: (list: HTMLUListElement) => void;
}

Calling AriaAutocomplete(element, options); returns the API object, which can also be accessed on the element via element.ariaAutocomplete.

changelog

Changelog

All notable changes to this project will be documented in this file.

[1.5.1] - 2023-12-23

Added

  • setOption(option, value) method to the API to fix issue that assigning values directly to the api.options object has no effect

[1.5.0] - 2023-03-04

Added

  • delete() method to the API to remove the latest entry. In multiple mode, can specify a selected entry. E.g. api.delete() to delete the last entry in the selected array, or api.delete(api.selected[0]) to delete a specific entry, or api.delete('some-value') to delete an entry with that specific value.
  • deleteAll() method to the API to delete all entries in multiple mode. If not in multiple mode, this will be the equivalent of calling delete() without an argument.

Chore

  • updated all dependencies

1.4.0 - 2021-03-27

Added

  • the ability to provide a function for the confirmOnBlur option that can return a string to compare against the search result labels.
  • page up and page down key handling within the options list to move the current focus position up or down by 10 options.
  • handling for disabled options, including ones based on disabled checkboxes and disabled select options.

Changed

  • adjusted the confirmOnBlur string matching behaviour to use the "cleaned" version of the search term and option label when blurring off of the field without a currently focused menu option.
  • defensive adjustment to setting the input value and moving focus to it after the component area is blurred.

Fixed

  • the create option when used as a function not being called with the API as context.

1.3.0 - 2021-03-07

Added

  • srDelay option to specify the delay before custom screen reader announcements are made. This now defaults to 1400 milliseconds, instead of 400 as before.
  • srAssistiveTextAutoClear option to allow the assistive text option to be removed when user input is detected, to decrease screen reader verbosity. The assistive text is re-associated with the generated input if its value is cleared.

Changed

  • increased the default srAutoClear value to 10000 milliseconds.

1.2.3 - 2020-11-01

Fixed

  • The create option not applying to starting values.
  • The create option persistence now correctly only applies when the autocomplete is used to progressively enhance a <select> or checkbox list.
  • Various HTML injection risks, particularly with the create and onItemRender options.

1.2.0 - 2020-10-31

Added

  • onChange callback option that fires when the selected item(s) changes, and provides all selections in an array as an argument.
  • onFocus callback option that fires when the overall component gains focus, and receives the component wrapper as an argument.
  • onBlur callback option that fires when the overall component loses focus, and receives the component wrapper as an argument.
  • id option to set a specific ID on the generated input
  • the function usage of the source option can now take a Promise which resolves with the items to render, instead of having to use the provided second argument callback
  • onAsyncBeforeSend callback option, to allow adjustments to the xhr object before it is sent (e.g. adding auth headers)
  • onAsyncComplete callback option, that fires after async call successfully completes and all items have rendered
  • srAutoClear option that takes a boolean, or number, to allow a delay before automatically clearing the screen reader announcement element - defaults to 5 seconds
  • deleteAllControl and deleteAllText options to render a button enabling quick deletion of all selected items (when there are at least 2 selected items)
  • create option to allow adding a results entry for the current search text (if no exact results match is found)
  • for all async related callbacks, and when the source is a function, there is now an additional final param that indicates if it is the first/starting call.
  • the selected items in multiple mode, and the show all button, will now have their aria-describedby set to link them to the control label

Changed

  • persist the aria-describedby attribute on the generated input, instead of removing it when the input has a value
  • in multiple mode, when deleting a selected item by clicking it or using enter, move focus to the next available selected item
  • set the aria-describedby attribute on the list container to reference the control's
  • do not hide the list when focus moves from the input to the show all control
  • moved the screen reader announcement element to be before the generated input, so that if users navigate past the input, they will not encounter the announcement element out of context

Fixed

  • The name option now works correctly.
  • In certain cases, the change event fired on the original input before the API's selected array was updated.
  • Added a workaround for an IE11 bug where the options were shown on load if the minLength was set to 0 on a multi-select autocomplete with starting values. This was due to the input's placeholder being removed, which erroneously triggers the input event in IE11.
  • Edge case errors when destroying the component immediately after certain actions (such as selecting an item, or blurring off of the component).
  • In multiple mode, moved the selected items to be after the list to fix issue on mobile when navigating by swipe, as it was possible to reach the selected items first, causing the list to disappear.
  • Issue with confirmOnBlur option not working correctly when a results option did not currently have focus.

1.1.4 - 2020-07-05

Fixed

  • TypeScript definitions not included in npm package

1.1.3 - 2020-02-04

Fixed

  • Issue where hitting the enter key after running a search with a value prevented the down arrow from moving focus to the first item in the results. Thanks to /u/holloway on reddit for discovering this.

1.1.2 - 2020-01-31

Fixed

  • Issue with autoGrow not triggering after deleting an item in multiple mode when the placeholder is re-added to the input

1.1.1 - 2020-01-29

Fixed

  • Issue where hide related classes were incorrectly being added to a select element's children, instead of to the select, due to a dependency issue

1.1.0 - 2020-01-29

Added

  • Home key usage to go to first item in the list
  • End key usage to go to last item in the list
  • onAsyncError callback option

Changed

  • Moved code and build over to TypeScript for: code improvements, self-documentation, and reduced bundle size by using an ES6 output from TypeScript that's bundled to UMD with webpack.

Fixed

  • Issue with the API filter method getting an error
  • Issue when clicking on a single-select autocomplete with minLength of 0 with a current selection, which was correctly searching with an empty string, but the polling method was then triggering a search with the value afterwards.
  • Screen reader announcements for results ignoring the number of results rendered