A flexible, accessible, customizable and efficient autocomplete input control with zero dependencies.

 Getting Started

To use AutoComple on your site, you need to import JavaScript/CSS assets and to instantiate the main class with an HTMLElement.

Installation

Install AutoComplete from npm package manager:

npm install @loicblascos/autocomplete --save
More information about the npm package can be found on npm repository.

Import

You can import polyfills, style and main class from package as follows:

import '@loicblascos/autocomplete/lib/polyfills'; // Optional
import '@loicblascos/autocomplete/lib/style.css'; // Optional
import AutoComplete from '@loicblascos/autocomplete';

Assets

If you do not use import, you need to include AutoComplete assets in the document:

<link rel="stylesheet" href="autocomplete.css">
<script src="autocomplete.js" async></script>

Markup

AutoComplete script works with search and text input elements. Explicit and Implicit input labels are supported by AutoComplete script to be fully accessible.

<!-- Explicit label -->
<label for="my-input">Input Label</label>
<input type="text" id="my-input" placeholder="type to search">

<!-- Implicit label -->
<label>
	Input Label
	<input type="text" id="my-input" placeholder="type to search">
</label>

Initialize

The AutoComplete() constructor accepts two arguments: the input element and an options object.

const input = document.querySelector( '#my-input' );
const acplt = new AutoComplete(
	input,
	{
		source: [
			{ label: 'label 1', value: 'value 1' },
			{ label: 'label 2', value: 'value 2' },
			{ label: 'label 3', value: 'value 3' },
		],
		minLength: 1,
		maxResults: 50,
		autoSearch: true,
	}
);

 Options

AutoComplete comes with plenty options to easily customize and extend possibilities.

PropertyTypeDefaultDescription
source{Object[]|fn}[]Holds list of suggestions to search for.
minLength{number}1Minimum number of characters to trigger a search.
maxResults{number}-1Maximum number of suggestions to display in the list.
regExp{string}($1)Expression used to match string(s) in suggestion.
filterResults{function}@see docFunction used to filter suggestions in the list.
sortResults{function}@see docFunction used to sort suggestions in the list.
renderItem{function}@see docFunction used to render suggestion in list item.
highlighter{HTMLElement}@see docElement used to append matched string(s).
menuTarget{HTMLElement}@see docElement used to append autocomplete menu.
smartAccent{boolean}falseOnly match accents from searched string.
matchAll{boolean}falseHighlight all matches string in suggestion.
autoFocus{boolean}trueAutomatically focus first suggestion in the list.
autoSearch{boolean}falseAutomatically searches for suggestions on focus.
autoComplete{boolean}falseInline auto complete first suggestion while typing.
loader{boolean}falseDisplay a loader while searching in source (async).
clearable{boolean}trueDisplay a button with cross icon to clear the field.
clearLabel{string}Clear fieldAccessible button label used in clear button.
classes{Object}@see docClass names used in AutoComplete elements.
messages{Object}@see docAccessible texts appended during user interactions.

source

AutoComplete source option accepts an array of strings, an array of objects. When using array of objects, label is used in suggestion and value set as input value on selection.

// Array of strings example.
new AutoComplete(
	input,
	{
		source: [ 'value 1', 'value 2', 'value 3' ],
	}
);

// Array of objects example.
new AutoComplete(
	input,
	{
		source: [
			{ label: 'label 1', value: 'value 1' },
			{ label: 'label 2', value: 'value 2' },
			{ label: 'label 3', value: 'value 3' },
		],
	}
);

When using array of objects, you can pass other properties and use them in the option renderItem. It can be useful to create custom markup with custom content that should not be directly searched.

If you need to make some data preprocessing or make asynchronous request to output suggestions, you can use a function and call the response callback passed as argument:

new AutoComplete(
	input,
	{
		/**
		 * Return suggestions after a search
		 *
		 * @param {string} value - Searched value.
		 * @param {function} response - Callback function.
		 */
		source: function( value, response ) {

			fetch( `https://api.github.com/search/repositories?q=${value}` )
			.then( obj => obj.json() )
			.then( data => {

				const source = data.items.map( item => item.full_name );
				response( source );

			} );

		},
		loader: true,
	}
);

filterResults

By default, results from suggestions are filtered by the label. You can use this option to add extra logic to match searched value in suggestions.

const menuTarget = document.querySelector( '.menu-target' );

new AutoComplete(
	input,
	{
		source: [ /* ... */ ],
		/**
		 * Filter by suggestion label and value
		 *
		 * @param {boolean} exists - Whether label matches value.
		 * @param {object} suggestion - label and value.
		 * @param {string} value - Searched value.
		 * @param {boolean} Return true to keep the suggestion, false otherwise.
		 */
		filterResults: function( exists, suggestion, value ) {

			// We normalize suggestion value (to remove diacritics, to trim, and to lower case)
			// And use regular expression generated from searched value.
			return exists || !! this.normalize( suggestion.value ).match( this.regExp );

		}
	}
);

sortResults

By default, suggestions are sorted by relevance. It means that suggestions are firstly ordered by matched string position, then alphabetically and by suggestion length. If you do not want to sort results you can simply set this option to false.

new AutoComplete(
	input,
	{
		source: [ /* ... */ ],
		// Default function used to sort.
		sortResults: ( a, b ) => {

			return (
				a.index - b.index ||                // Sort by matched index.
				a.label.localeCompare( b.label ) || // Then, sort alphabetically.
				a.label.length - b.label.length     // Then, sort by string length.
			);
		}
	}
);

renderItem

By default, suggestions output string in list item and cannot output HTML for performance and security reasons (XSS vulnerability from remote source for example). However, you can control the rendered content in each list item thanks to this option.

renderItem option accepts a function with as arguments the suggestion (from source) and the item element from the list. The function should return an HTMLelement or string to append in the list item. By returning null it will skip the item from the list.

const colorSwatch = document.createElement( 'span' );

new AutoComplete(
	input,
	{
		source: [
			{ label: '#f0f8ff - aliceblue', value: '#f0f8ff' },
			{ label: '#faebd7 - antiquewhite', value: '#faebd7' },
			{ label: '#00ffff - aqua', value: '#00ffff' },
			{ label: '#7fffd4 - aquamarine', value: '#7fffd4' },
			{ label: '#f0ffff - azure', value: '#f0ffff' },
			{ label: '#f5f5dc - beige', value: '#f5f5dc' },
			{ label: '#ffe4c4 - bisque', value: '#ffe4c4' },
			{ label: '#000000 - black', value: '#000000' },
			/* ... */
		],
		maxResults: 50,
		renderItem: ( suggestion, item ) => {

			// We append color swatch directly in list item.
			item.append( colorSwatch.cloneNode() );
			// We set css variable on list item.
			item.style.setProperty( '--color', suggestion.value );


			// We return the highlighted suggestion content to append in item.
			// Content is a documentFragment because highlight option is enabled.
			// Otherwise it'll be a string and it'll erase our appended element.
			return suggestion.content;

		}
	}
);
.acplt-list .acplt-item[style^="--color"] {
	display: flex;
	color: black;
	font-family: monospace;
	white-space: pre;
}

.acplt-list .acplt-item[style^="--color"] span {
	width: 1rem;
	height: 1rem;
	margin-right: 0.5rem;
	background-color: var(--color);
	border-radius: 100%;
	box-shadow: 0 0 0 1px #e6ecf1;
}

highlighter

By default, the highlighter use a <mark> element to append matched string(s), from searched value, in suggestions. You can use any HTMLElement to highlight matched string(s) in suggestion. highlighter can be set to false to disable this feature.

const highlighter = document.createElement( 'mark' );
highlighter.className = 'highlighter';

new AutoComplete(
	input,
	{
		source: [ /* ... */ ],
		highlighter,
	}
);
.acplt-item .highlighter {
	background-color: #85ffff;
}

.acplt-item .highlighter::before,
.acplt-item .highlighter::after {
	content: "ยท";
	background-color: white;
}

.acplt-item[aria-selected=true] .highlighter::before,
.acplt-item[aria-selected=true] .highlighter::after {
	background-color: #e9f2ff;
}

By default, the menu is appended in document.body element. But, it may conflict with your layout, so you can set the element in which you want to append the menu thanks to this option. By changing the menu target and by playing on menu style it's even possible to inline it in the document.

const menuTarget = document.querySelector( '.menu-target' );

new AutoComplete(
	input,
	{
		source: [ /* ... */ ],
		menuTarget,
		minLength: 0,
		maxResults: 5,
		autoSearch: true,
		onResize: ( { style } ) => {

			style.position = 'relative';
			style.zIndex = 0;
			style.left = 0;
			style.top = 0;

		},
	}
);

classes

Because conflicts may happen with class names or simply because you need to use others, you can replace all classes used by the script thanks to this options:

new AutoComplete(
	input,
	{
		// Default classes.
		classes: {
			wrapper: 'acplt',
			menu: 'acplt-menu',
			list: 'acplt-list',
			item: 'acplt-item',
			clear: 'acplt-clear',
			loader: 'acplt-loader',
			message: 'acplt-message',
		},
	}
);

messages

AutoComplete script is accessible and follows W3 ARIA practices. It appends messages on user interactions that can be announced by assistive technologies. You can easily localize these messages thanks to this option:

new AutoComplete(
	input,
	{
		// Default messages.
		messages: {
			open: 'Use Up and Down to choose suggestions and press Enter to select suggestion.',
			input: 'Type to search or press Escape to clear the input.',
			clear: 'Field cleared.',
			select: '%s suggestion was selected.',
			noResults: 'No suggestions found.',
			loading: 'Loading suggestions...',
		},
	}
);

 Methods

AutoComplete comes with several public methods that you can call at any time after instantiation. The following methods allow to programmatically interact with input and menu.

NameArgumentTypeDescription
destroy--Remove functionalities and instance attached to the input.
enable--Remove disable attribute from input and allow search.
disable--Add disable attribute and prevent user to search.
open--Open menu of suggestions from the last search.
close--Close menu of suggestions.
clear--Clear input value, close menu and update state.
searchvalue{string}Trigger a manual search from a string.
selectindex{number}Select a suggestion in the menu from an index.
highlightindex{number}Highlight a suggestion in the menu from an index.

destroy()

Remove AutoComplete functionality and instance attached to the input. destroy() will return the element back to its pre-initialized state.

const init = document.querySelector( '#init-btn' );
const destroy = document.querySelector( '#destroy-btn' );
const instantiate = () => instance = new AutoComplete( input, { source: [ /* ... */ ] } );
let instance;

instantiate();
init.addEventListener( 'click', instantiate );
destroy.addEventListener( 'click', () => instance && instance.destroy() );

enable() / disable()

While it's possible to directly disable/enable the input with disabled attribute, these methods will handle other aspects like closing the menu or updating clear button.

const enable = document.querySelector( '#enable-btn' );
const disable = document.querySelector( '#disable-btn' );
const instance = new AutoComplete( input, { source: [ /* ... */ ] } );

enable.addEventListener( 'click', () => instance.enable() );
disable.addEventListener( 'click', () => instance.disable() );

open() / close()

open method allows to open suggestions from last search if it exists. In this example, we populate the list of suggestion from empty searched string to simulate a search history.

const open = document.querySelector( '#open-btn' );
const close = document.querySelector( '#close-btn' );
const instance = new AutoComplete( input, { source: [ /* ... */ ] } );

// We populate the list of suggestions for demo purpose.
instance.populate( '', instance.options.source );

open.addEventListener( 'click', () => instance.open() );
close.addEventListener( 'click', () => instance.close() );

search() / clear()

search() method accepts as argument a string to programmatically trigger a search from a string. It allows to search in suggestions and to open menu if there are results. You can also use clear() method to clear a search (clear input value and close menu).

const search = document.querySelector( '#search-btn' );
const clear = document.querySelector( '#clear-btn' );
const instance = new AutoComplete( input, { source: [ /* ... */ ] } );

search.addEventListener( 'click', () => {

	input.value = 'saint';
	instance.search( 'saint' );

} );

clear.addEventListener( 'click', () => instance.clear() );

select() / highlight()

select() and highlight() methods accept as argument a number to programmatically select or highlight a suggestion from an index.

const select = document.querySelector( '#select-btn' );
const moveUp = document.querySelector( '#moveup-btn' );
const moveDown = document.querySelector( '#movedown-btn' );
const menuTarget = document.querySelector( '#menu-target' );
const instance = new AutoComplete(
	input,
	{
        source: [ /* ... */ ],
		menuTarget,
		minLength: 0,
		maxResults: 20,
		onResize: ( { style } ) => {

			style.position = 'relative';
			style.zIndex = 0;
			style.left = 0;
			style.top = 0;

		},
	}
);

// For demo purpose.
instance.close = () => null;
instance.disable();
instance.search();

select.addEventListener( 'click', () => instance.select( instance.index ) );
moveUp.addEventListener( 'click', () => instance.highlight( instance.index - 1 ) );
moveDown.addEventListener( 'click', () => instance.highlight( instance.index + 1 ) );

 Events

AutoComplete comes with several events triggered after actions that you can easily capture.

NameArgumentTypeDescription
onOpen--Triggered after the menu was opened.
onClose--Triggered after the menu was closes.
onResize--Triggered after the menu is resized.
onSearchvalue{string}Triggered before a search is performed.
onAbort--Triggered when a search is aborted (async).
onClear--Triggered after the input has been cleared.
onSelectitem{Object}Triggered after a suggestion has been selected.
onHighlightitem{object}Triggered after a suggestion has been highlighted.

Events Logger

### Events Logger ###
new AutoComplete(
	input,
	{
		source: [ /* ... */ ],
		onOpen: () => console.log( 'menu opened' ),
		onClose: () => console.log( 'menu closed' ),
		onResize: () => console.log( 'menu resized' ),
		onSearch: value => console.log( `search for "${ value }"` ),
		onAbort: () => console.log( 'search aborted' ),
		onClear: () => console.log( 'input cleared' ),
		onSelect: item => console.log( `"${ item.label }" selected` ),
		onHighlight: item => console.log( `"${ item.label }" highlighted` ),
	}
);

 Extras

AutoComplete comes with several public methods and events that allow to go a step beyond depending of your needs. In the following examples, we will change the default behaviour of a search.

Multiple values

source option allows to control the searched suggestions from the input value. So, if we split the value argument passed in source function and pass it to response() method, it will automatically make a new search from last splitted value.

In this example, we use internal method response( value, suggestions ), that accepts as first argument the searched value and as second argument the suggestions in which to search.

In source function (and all events), instance context is binded (this). So, you should not use arrow function if you want to keep instance context.

new AutoComplete(
	input,
	{
		source: function( value ) {

			this.values = value.trim().split( /s*,s*/ );
			// We use internal method instead of source callback.
			this.response( this.values.pop(), source );

		},
		onSelect: function( { label } ) {

			this.values.push( label );
			input.value = `${ this.values.filter( Boolean ).join( ', ' ) }, `;

		},
	}
);

Dynamic values

In this example, the source is dynamic and it passes in response callback suggestion based on user input (value). We also render the suggestion as shown in renderItem (@see example)

const colorSwatch = document.createElement( 'span' );

new AutoComplete(
	input,
	{
		source: ( value, response ) => {

			const isHex = /^#([0-9A-F]{3}){1,2}$/i.test( value );
			response( isHex ? [ value ] : [] );

		},
		renderItem: ( suggestion, item ) => {

			item.style.setProperty( '--color', suggestion.value );
			item.append( colorSwatch.cloneNode() );

			return suggestion.content;

		},
	}
);