AutoCompleteInput
Text input with a suggestion dropdown. Shares the same row layout and styling primitives as Input (label, icons, validation, clear button), with an animated suggestion panel underneath that expands/collapses to its measured content height.
The suggestion panel renders its rows in a ScrollView. If AutoCompleteInput sits inside your own ScrollContainer/ScrollView (the common case for forms), that outer ScrollView must also have keyboardShouldPersistTaps="handled". Without it, the outer ScrollView swallows the first tap on a suggestion to dismiss the keyboard instead of passing it through — so selection silently does nothing.
<ScrollContainer keyboardShouldPersistTaps="handled">
<AutoCompleteInput ... />
</ScrollContainer>
Basic Usage
import { useState } from 'react';
import { AutoCompleteInput } from '@multinaire/expo-ui';
const CITIES = ['Bangkok', 'Berlin', 'Boston', 'Brisbane', 'Budapest'];
function CityField() {
const [value, setValue] = useState('');
return (
<AutoCompleteInput
placeholder="Search a city..."
leading="Location"
value={value}
onChangeText={setValue}
items={CITIES.map(city => ({ text: city, value: city }))}
searchPredicate={(city, filter) =>
city.toLowerCase().includes(filter.toLowerCase())
}
onChange={city => console.log('Selected:', city)}
/>
);
}
Props
| Prop | Type | Default | Description |
|---|---|---|---|
items | AutoCompleteInputItemData<T>[] | — | Suggestions shown below the input |
value | string | — | Current value of the input |
onChangeText | (text: string) => void | — | Callback invoked on every text change |
selectedItem | T | — | Currently selected item value. Compared against each suggestion's value to highlight the matching one. A suggestion is also highlighted when its text matches the current input value |
onChange | (value: T, item: AutoCompleteInputItemData<T>) => void | — | Callback invoked when a suggestion is selected. The input is automatically filled with the suggestion's text |
onClear | () => void | — | Callback invoked when the clear button is pressed. Use this to also reset any externally-held selectedItem state |
searchPredicate | (item: T, filter: string) => boolean | — | Client-side filter run against items for the current value. Omit when items is already filtered externally (e.g. a server-side search wired up via onChangeText) |
isLoading | boolean | false | Show a loading state in place of the suggestion list (e.g. while fetching results) |
minCharacters | number | 1 | Minimum input length before suggestions are shown |
maxVisibleItems | number | 5 | Maximum number of suggestion rows visible before the list scrolls |
noResultsText | string | — | Text shown when there are no matching suggestions |
highlightMatch | boolean | true | Highlight the portion of each suggestion's text that matches the current input value |
title | string | — | Field label displayed above the input |
required | boolean | false | Mark the field as required. Displays a visual indicator |
allowClear | boolean | false | Show a clear (×) button to reset the value |
leading | IconName | ReactElement | — | Leading icon name or a custom React element rendered at the start of the input |
trailing | IconName | ReactElement | — | Trailing icon name or a custom React element rendered at the end of the input |
errorText | string | — | Error message displayed below the input |
keyboardType | KeyboardTypeOptions | — | Keyboard type shown when the input is focused |
flex | number | — | Flex grow value |
margin | SpacingProps | — | Outer margin |
testID | string | — | Test identifier for UI automation |
Examples
Async / Server-Side Search
Omit searchPredicate and pass in already-filtered items from your own fetch logic; toggle isLoading while the request is in flight.
const [value, setValue] = useState('');
const [items, setItems] = useState<AutoCompleteInputItemData<string>[]>([]);
const [isLoading, setIsLoading] = useState(false);
<AutoCompleteInput
placeholder="Search users..."
value={value}
onChangeText={async text => {
setValue(text);
setIsLoading(true);
const results = await searchUsers(text);
setItems(results.map(user => ({ text: user.name, value: user.id })));
setIsLoading(false);
}}
items={items}
isLoading={isLoading}
onChange={userId => console.log('Selected user:', userId)}
/>
With Leading Icons per Suggestion
<AutoCompleteInput
placeholder="Add a member..."
value={value}
onChangeText={setValue}
items={members.map(member => ({
text: member.name,
value: member.id,
leading: member.avatar,
}))}
searchPredicate={(id, filter) =>
members
.find(m => m.id === id)!
.name.toLowerCase()
.includes(filter.toLowerCase())
}
onChange={id => addMember(id)}
/>
No Results / Minimum Characters
<AutoCompleteInput
placeholder="Search a city..."
value={value}
onChangeText={setValue}
items={items}
searchPredicate={(city, filter) =>
city.toLowerCase().includes(filter.toLowerCase())
}
minCharacters={2}
noResultsText="No cities found"
onChange={setSelectedCity}
/>