Import#
import { Autocomplete } from '@dnb/eufemia'
Description#
The Autocomplete component is a combination of an Input and a Dropdown, also called ComboBox. During typing, matching data items get suggested in an option menu (listbox).
When to use Autocomplete vs Eufemia Forms#
Classic form components like this one are presentational controls. They handle the styling, sizing, icons, and basic events, while you manage their value, validation, and error handling yourself.
For most data input and forms situations, use Eufemia Forms fields instead. They build on these same components, but add data handling, validation, and error messages through the surrounding Form.Handler. Browse the field components to find the one that matches your data.
Reach for a classic component when you need it standalone outside of a form context, or when you handle the value and validation yourself.
The Eufemia Forms equivalent of Autocomplete is Field.Selection with the autocomplete variant (variant="autocomplete"), or Field.ArraySelection for selecting multiple values.
Relevant links#
Typeahead and ComboBox#
The Autocomplete component may also be known as Typeahead or ComboBox. But autocomplete describes the purpose more precisely and descriptively, therefore Eufemia uses this term.
When to use it#
Use it for both small autocomplete purposes and large (async) data set searches. The component supports two ways of showing ProgressIndicator.
You may check out the Dropdown component for more details on how to use it. They both share the same DrawerList.
Highlighting#
Words found during typing are highlighted. The rules are:
- The first two words will match the beginning of a word
- The third word will match inside a word (can be changed with
search={{ matchInsideWordsFrom: number }}) - Case-insensitive
To only match items that begin with the first typed word, set search={{ match: "starts-with" }}.
Using Components inside content#
It is not possible to wrap them inside React Components. The reason is that the Autocomplete component needs to know what data it wants to search for before your React Component has rendered. Additionally, the component cannot update the HTML to make the bold highlighting after your component has rendered.
That means you cannot run a component that will render as soon as it is displayed.
If you need to format numbers, then do it before you send in the data content.
It is possible to wrap your content inside one HTML Element. Nested elements are not supported.
To wrap your content only visually, you can provide your wrappers inside an array:
or you can provide it inside a fragment:
and if you need to decouple the searchable content from what's displayed, then you can put your searchable content inside searchContent:
Re-render data#
For performance optimization, you should ensure the data array/object is memoized (with useMemo, useState, or useRef), so when the Autocomplete re-renders, it does not have to process the internal data unnecessarily.
const MyComponent = () => {const data = React.useMemo(() => ['Item 1', 'Item 2'], [])return <Autocomplete data={data} />}
Or keep it outside the component:
const data = ['Item 1', 'Item 2']const MyComponent = () => {return <Autocomplete data={data} />}
Numbers#
Numbers are often different from a word filter. You can use search={{ numbers: true }} to enable number-specialized filtering. See examples in the demos.
Now the user could search for e.g. bank account numbers by just entering 201, even if you format it like 2000 12 34567 (e.g. use format(20001234567, { ban: true }) from @dnb/eufemia/components/number-format/NumberUtils).
Screen reader support#
To enhance screen reader usage, this component uses aria-live to announce the number of options found (ariaLiveOptions).
Custom size#
.dnb-autocomplete {--autocomplete-width: 20rem; /* custom width */}
You can also set the width directly, but then it has to be defined like so (including min-width):
/** Because of the included label/status etc. we target the "__shell" */.dnb-autocomplete__shell {width: 10rem;}/** In order to change only the drawer-list width */.dnb-autocomplete .dnb-drawer-list__root {width: 10rem;}
Dynamically change data#
You can manipulate the used data dynamically, either by changing the data property or during user events like onType or onFocus. The following properties and methods are there to use:
Methods#
updateDatareplace all data entries.emptyDataremove all data entries.resetSelectedItemwill invalidate the selected key.revalidateSelectedItemwill re-validate the internal selected key on the givenvalue.revalidateInputValuewill re-validate the current input value and update it – based on the givenvalue.setInputValueupdate the input value.clearInputValuewill set the current input value to an empty string.focusInputwill set focus on the input element.showIndicatorshows a progress indicator instead of the icon (inside the input). Whenicon={null}is set, no progress indicator is shown.hideIndicatorhides the progress indicator inside the input.showIndicatorItemshows an item with a ProgressIndicator status as a data option item.showNoOptionsItemshows the "no entries found" status as a data option item.setVisibleshows the DrawerList.setHiddenhides the DrawerList.showAllItemsshows all DrawerList items.setModeswitches the mode during runtime.debouncea debounce method with a cancel invocation method on repeating calls. There is more documentation about this method.
Properties#
dataListcontains all the data entries.
Example#
<AutocompleteonFocus={({ updateData, showIndicator }) => {showIndicator()setTimeout(() => {updateData(topMovies)}, 1e3)}}onType={({ value /* updateData, ... */ }) => {console.log('onType', value)}}/>
Root Element (React Portal)#
The Autocomplete component uses PortalRoot internally to render its option list. See the PortalRoot documentation for information on how to control where the portal content appears in the DOM, and for the BrowserTranslate helper when browser translation tools such as Google Translate should not modify content rendered through PortalRoot.
Related components#
Autocomplete is part of the Input category. Other components for similar needs:
- Checkbox — when people can turn one or more options on or off.
- DatePicker — when people need to choose one date or a date range.
- Dropdown — when people need to choose one option from a list.
- Filter — to help people narrow down a list or data set.
- FormLabel — to name an input, control, or form-related field.
- Input — when people need to enter a short line of text.
Demos#
Default autocomplete#
<Autocomplete data={topMovies} label="Label" />
Large data sets#
Virtualization is opt-in. Import the driver from @dnb/eufemia/fragments/drawer-list/Virtualization and pass it as listDriver. The default Autocomplete bundle does not import the virtualization dependency. Filtering and “Show all” continue to operate on the complete data set while only the visible options are rendered.
Install the optional peer dependency before using the driver:
yarn add @tanstack/react-virtual
import { Autocomplete } from '@dnb/eufemia/components'import { createDrawerListVirtualization } from '@dnb/eufemia/fragments/drawer-list/Virtualization'const listDriver = createDrawerListVirtualization()<Autocomplete data={items} listDriver={listDriver} />
<Autocomplete label="Search 10,000 items" data={virtualizedItems} listDriver={virtualizedList} showSubmitButton />
Autocomplete with numbers#
This example uses search={{ numbers: true }} to enable number-optimized matching.
<Autocomplete inputValue="201" showClearButton label="Label" data={numbersData} search={{ numbers: true, }} />
Search options#
Use the search prop to configure search behavior. This example uses match: 'starts-with' to only show results that begin with the typed text, and highlight: false to disable text highlighting.
<Autocomplete label="Search movies (starts-with)" data={topMovies} search={{ match: 'starts-with', highlight: false, }} />
Autocomplete with a custom title#
keepValuemeans the input value gets not removed after an input blur happens.showClearButtonmeans a clear button will show up when the input field contains a value.
<Autocomplete data={topMovies} keepValue={true} showClearButton={true} label="Label" placeholder="Custom placeholder ..." onChange={({ data }) => { console.log('onChange', data) }} />
Async usage, dynamically update data during typing#
This example simulates server delay with a timeout and - if it gets debounced, we cancel the timeout. Read more about the debounce method.
Also, you may consider using search={{ filter: false }} if you have a backend doing the search operation.
const onTypeHandler = ({ value, showIndicator, hideIndicator, updateData, showNoOptionsItem, debounce, /* ... */ }) => { console.log('typed value:', value) showIndicator() debounce( ({ value }) => { console.log('debounced value:', value) const normalizedValue = value.trim().toLowerCase() const filteredData = topMovies.filter(({ content }) => { if (typeof content === 'string') { return content.toLowerCase().includes(normalizedValue) } if (Array.isArray(content)) { return content .filter((part) => typeof part === 'string') .join(' ') .toLowerCase() .includes(normalizedValue) } return false }) const newData = normalizedValue.length > 0 ? filteredData : topMovies // simulate server delay const timeout = setTimeout(() => { // update the drawerList updateData(newData) hideIndicator() if (newData.length === 0) { showNoOptionsItem() } }, 600) // cancel invocation method return () => clearTimeout(timeout) }, { value, }, 250 ) } render( <Autocomplete mode="async" onType={onTypeHandler} noScrollAnimation={true} placeholder="Search ..." /> )
Update data dynamically on the first focus#
const onFocusHandler = ({ updateData, dataList, showIndicatorItem }) => { if (!dataList.length) { showIndicatorItem() setTimeout(() => { updateData(topMovies) }, 1e3) } } render( <Autocomplete mode="async" noScrollAnimation={true} preventSelection={true} onType={({ value /* updateData, ... */ }) => { console.log('onType', value) }} onFocus={onFocusHandler} /> )
With a Button to toggle the open / close state#
NB: Just to show the possibility; the data is given as a function.
<Autocomplete label="Label" value={10} showSubmitButton={true} onChange={({ data }) => { console.log('onChange', data) }} > {() => topMovies} </Autocomplete>
With a predefined input/search value#
<Autocomplete label="Label" inputValue="the pa ther" noAnimation onChange={({ data }) => { console.log('onChange', data) }} > {() => topMovies} </Autocomplete>
Inline results#
Use the inline property to render the results list persistently open in normal document flow, instead of as an overlay. The toggle button is hidden, while filtering, highlighting, selection and keyboard navigation stay the same.
<Autocomplete inline stretch label="Label" data={topMovies} />
Different sizes#
Four sizes are available: small, default, medium and large.
<Flex.Vertical> <Autocomplete label="Label" size="default" data={() => topMovies} /> <Autocomplete label="Label" size="medium" data={() => topMovies} /> <Autocomplete label="Label" size="large" data={() => topMovies} /> </Flex.Vertical>
Data suffix value#
Data is provided as such:
const { locale } = React.useContext(Context)const data = [{suffixValue: (<NumberFormat.Currency srLabel="Total:" locale={locale}>{12345678}</NumberFormat.Currency>),selectedValue: `Brukskonto (${ban})`,content: ['Brukskonto', ban],},]
const CustomWidth = styled(Autocomplete)` .dnb-drawer-list__root, .dnb-autocomplete__shell { width: 50vw; min-width: 15rem; max-width: 30rem; } ` render( <CustomWidth value={1} data={numbers} size="medium" icon={null} showSubmitButton label="From account" /> )
Custom width#
const CustomWidthOne = styled(Autocomplete)` .dnb-autocomplete__shell { width: 10rem; } ` const CustomWidthTwo = styled(Autocomplete)` &.dnb-autocomplete--is-popup .dnb-drawer-list__root { width: 12rem; } ` const CustomWidthThree = styled(Autocomplete)` /** Change the "__shell" width */ .dnb-autocomplete__shell { width: 12rem; } /** Change the "__list" width */ .dnb-drawer-list__root { width: 20rem; } ` render( <Flex.Vertical> <CustomWidthOne label="Label" labelSrOnly size="default" iconPosition="left" data={topMovies} /> <CustomWidthTwo label="Label" labelSrOnly size="medium" data={topMovies} /> <CustomWidthThree label="Label" labelSrOnly size="large" align="right" iconPosition="right" icon="bell" data={topMovies} /> </Flex.Vertical> )
Autocomplete with status message#
<Autocomplete data={topMovies} label="Label" status="You need to select a movie" statusState="information" showSubmitButton />
Autocomplete with List item content#
Reuse the List row layout for rich option content. The option is already an <li> and wraps its content in <span> elements, so use element="span" on List.Item.Basic and its cells to keep the markup valid. Give the Autocomplete a width that fits both the title and the end value. Provide selectedValue with the plain text so the input shows a sensible value once an option is selected, and searchContent so typing still filters the options. See rendering a row outside a List.Container for the details.
// A List row keeps its end cell at content width, so the row needs // horizontal room. Give the Autocomplete a width of its own, or the // title column will be squeezed by the end cell. const AccountAutocomplete = styled(Autocomplete)` .dnb-autocomplete__shell, .dnb-drawer-list__root { width: 22rem; } ` const data = [ { selectedKey: 'accounts', // selectedValue is the plain text shown in the input once selected; // searchContent keeps typing/filtering working with rich content selectedValue: 'Accounts', searchContent: 'Accounts Bills Savings', content: ( <List.Item.Basic element="span"> <List.Cell.Title element="span">Accounts</List.Cell.Title> <List.Cell.End element="span">Bills, Savings</List.Cell.End> </List.Item.Basic> ), }, { selectedKey: 'loans', selectedValue: 'Loans', searchContent: 'Loans Mortgage Car', content: ( <List.Item.Basic element="span"> <List.Cell.Title element="span">Loans</List.Cell.Title> <List.Cell.End element="span">Mortgage, Car</List.Cell.End> </List.Item.Basic> ), }, { selectedKey: 'cards', selectedValue: 'Cards', searchContent: 'Cards Visa Mastercard', content: ( <List.Item.Basic element="span"> <List.Cell.Title element="span">Cards</List.Cell.Title> <List.Cell.End element="span">Visa, Mastercard</List.Cell.End> </List.Item.Basic> ), }, ] render(<AccountAutocomplete data={data} label="Label" />)
Groups#
If an item has a groupIndex property, it will use the groups in the groups property. Only the first group can be without title, all other groups must have a title.
<Autocomplete groups={[undefined, 'Pets', 'Cars']} data={[ { groupIndex: 0, content: 'Default 1', }, { groupIndex: 0, content: 'Default 2', }, { groupIndex: 1, content: 'Cat', }, { groupIndex: 1, content: 'Dog', }, { groupIndex: 2, content: 'Jeep', }, { groupIndex: 2, content: 'Van', }, ]} />
No divider#
We can remove the divider between items with the noDivider prop. Beware that this can make information dense lists difficult to parse.
<Autocomplete noDivider data={['Cat', 'Dog', 'Canary', 'Hamster', 'Piglet']} />