Skip to content

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:

  1. The first two words will match the beginning of a word
  2. The third word will match inside a word (can be changed with searchInWordIndex)
  3. Case-insensitive

To only match items that begin with the first typed word, set searchMatch="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:

<Autocomplete
  data={[
    {
      content: [
        <IconPrimary icon="bell" key="item-1" />,
        <span className="custom-selector-a" key="item-2">
          The Shawshank Redemption
        </span>,
        <span className="custom-selector-b" key="item-3">
          The Dark Knight
        </span>,
        // etc.
        <NumberFormat.Number value={1234} key="item-4" />, // <-- Not searchable nor highlightable
      ],
    },
  ]}
  label="Label"
/>

or you can provide it inside a fragment:

<Autocomplete
  data={[
    {
      content: (
        <>
          <IconPrimary icon="bell" />
          <span className="custom-selector-a">
            The Shawshank Redemption
          </span>
          <span className="custom-selector-b">The Dark Knight</span>
        </>
      ),
    },
  ]}
  label="Label"
/>

and if you need to decouple the searchable content from what's displayed, then you can put your searchable content inside searchContent:

<Autocomplete
  data={[
    {
      content: ['your visual content'],
      searchContent: ['your search content'],
    },
  ]}
  label="Label"
/>

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 searchNumbers={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

  • updateData replace all data entries.
  • emptyData remove all data entries.
  • resetSelectedItem will invalidate the selected key.
  • revalidateSelectedItem will re-validate the internal selected key on the given value.
  • revalidateInputValue will re-validate the current input value and update it – based on the given value.
  • setInputValue update the input value.
  • clearInputValue will set the current input value to an empty string.
  • focusInput will set focus on the input element.
  • showIndicator shows a progress indicator instead of the icon (inside the input). When icon={null} is set, no progress indicator is shown.
  • hideIndicator hides the progress indicator inside the input.
  • showIndicatorItem shows an item with a ProgressIndicator status as a data option item.
  • showNoOptionsItem shows the "no entries found" status as a data option item.
  • setVisible shows the DrawerList.
  • setHidden hides the DrawerList.
  • showAllItems shows all DrawerList items.
  • setMode switches the mode during runtime.
  • debounce a debounce method with a cancel invocation method on repeating calls. There is more documentation about this method.

Properties

  • dataList contains all the data entries.

Example

<Autocomplete
onFocus={({ 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.

See all in Input →

Demos

Default autocomplete

<Autocomplete data={topMovies} label="Label" />

Autocomplete with numbers

<Autocomplete
  inputValue="201"
  showClearButton
  label="Label"
  data={numbersData}
  searchNumbers={true}
/>

Autocomplete with a custom title

  • keepValue means the input value gets not removed after an input blur happens.
  • showClearButton means 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 disableFilter 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.

  • The Shawshank Redemption
  • The GodfatherLine with more info
  • The Godfather: Part IIAnchor 1Anchor 2Line with more info
  • The Dark Knight
  • 12 Angry MenSecond rowThird row
  • Schindler's List
  • Pulp Fiction
  • The Lord of the Rings: The Return of the King
  • The Good, the Bad and the Ugly
  • Fight Club
  • The Lord of the Rings: The Fellowship of the Ring
  • Star Wars: Episode V - The Empire Strikes Back
  • Forrest Gump
  • Inception
  • The Lord of the Rings: The Two Towers
  • One Flew Over the Cuckoo's Nest
  • Goodfellas
  • The Matrix
  • Seven Samurai
  • Star Wars: Episode IV - A New Hope
  • City of God
  • Se7en
  • The Silence of the Lambs
  • It's a Wonderful Life
  • Life Is Beautiful
  • The Usual Suspects
  • Léon: The Professional
  • Spirited Away
  • Saving Private Ryan
  • Once Upon a Time in the West
  • American History X
  • Interstellar
  • Casablanca
  • City Lights
  • Psycho
  • The Green Mile
  • The Intouchables
  • Modern Times
  • Raiders of the Lost Ark
  • Rear Window
  • The Pianist
  • The Departed
  • Terminator 2: Judgment Day
  • Back to the Future
  • Whiplash
  • Gladiator
  • Memento
  • The Prestige
  • The Lion King
  • Apocalypse Now
  • Alien
  • Sunset Boulevard
  • Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb
  • The Great Dictator
  • Cinema Paradiso
  • The Lives of Others
  • Grave of the Fireflies
  • Paths of Glory
  • Django Unchained
  • The Shining
  • WALL·E
  • American Beauty
  • The Dark Knight Rises
  • Princess Mononoke
  • Aliens
  • Oldboy
  • Once Upon a Time in America
  • Witness for the Prosecution
  • Das Boot
  • Citizen Kane
  • North by Northwest
  • Vertigo
  • Star Wars: Episode VI - Return of the Jedi
  • Reservoir Dogs
  • Braveheart
  • M
  • Requiem for a Dream
  • Amélie
  • A Clockwork Orange
  • Like Stars on Earth
  • Taxi Driver
  • Lawrence of Arabia
  • Double Indemnity
  • Eternal Sunshine of the Spotless Mind
  • Amadeus
  • To Kill a Mockingbird
  • Toy Story 3
  • Logan
  • Full Metal Jacket
  • Dangal
  • The Sting
  • 2001: A Space Odyssey
  • Singin' in the Rain
  • Toy Story
  • Bicycle Thieves
  • The Kid
  • Inglourious Basterds
  • Snatch
  • 3 Idiots
  • Monty Python and the Holy Grail
<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],
},
]
12 345 678,00 kr
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

You need to select a movie
<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']}
/>
Edit on GitHub