AutoComplete
Examples
(9)Properties
(92)Playground
Basic
Loading demo…
import
{
useMemo
,
useState
}
from
'react'
;
import
{
AutoComplete
,
Block
}
from
'@platform-blocks/react-ui-library'
;
import
type
{
AutoCompleteOption
}
from
'@platform-blocks/react-ui-library'
;
import
{
sports
}
from
'../data'
;
export
function
Demo
(
)
{
const
[
inputValue
,
setInputValue
]
=
useState
(
''
)
;
const
[
selectedSport
,
setSelectedSport
]
=
useState
<AutoCompleteOption
|
null
>
(
null
)
;
const
displayValue
=
useMemo
(
(
)
=>
selectedSport
?
.
label
??
inputValue
,
[
selectedSport
,
inputValue
]
)
;
return
(
<Block
w
=
{
400
}
>
<AutoComplete
label
=
"Choose a sport"
placeholder
=
"Search for a sport..."
data
=
{
sports
}
value
=
{
displayValue
}
onChangeText
=
{
(
value
)
=>
{
setInputValue
(
value
)
;
if
(
!
value
)
setSelectedSport
(
null
)
;
}
}
onSelect
=
{
(
item
)
=>
{
setSelectedSport
(
item
)
;
setInputValue
(
item
.
label
)
;
}
}
displayProperty
=
"label"
minSearchLength
=
{
1
}
/>
</Block
>
)
;
}
Multi-select tags
Loading demo…
import
{
useState
}
from
'react'
import
{
AutoComplete
,
Block
,
Chip
,
Icon
}
from
'@platform-blocks/react-ui-library'
;
import
type
{
AutoCompleteOption
}
from
'@platform-blocks/react-ui-library'
;
import
{
musicGenres
}
from
'../data'
export
function
Demo
(
)
{
const
[
inputValue
,
setInputValue
]
=
useState
(
''
)
const
[
selectedGenres
,
setSelectedGenres
]
=
useState
<AutoCompleteOption
[
]
>
(
[
]
)
const
handleToggle
=
(
option
:
AutoCompleteOption
)
=>
{
const
isSelected
=
selectedGenres
.
some
(
(
genre
)
=>
genre
.
value
===
option
.
value
)
setSelectedGenres
(
(
current
)
=>
isSelected
?
current
.
filter
(
(
genre
)
=>
genre
.
value
!==
option
.
value
)
:
[
.
.
.
current
,
option
]
,
)
}
return
(
<Block
w
=
{
400
}
>
<AutoComplete
label
=
"Music genres"
placeholder
=
"Search genres..."
data
=
{
musicGenres
}
value
=
{
inputValue
}
onChangeText
=
{
setInputValue
}
onSelect
=
{
handleToggle
}
multiSelect
selectedValues
=
{
selectedGenres
}
minSearchLength
=
{
0
}
clearable
onClear
=
{
(
)
=>
{
setSelectedGenres
(
[
]
)
setInputValue
(
''
)
}
}
selectedValuesContainerStyle
=
{
{
flexWrap
:
'wrap'
,
gap
:
6
}
}
renderSelectedValue
=
{
(
item
,
_index
,
helpers
)
=>
(
<Chip
key
=
{
item
.
value
}
size
=
"sm"
variant
=
"surface"
endIcon
=
{
<Icon
name
=
"x"
size
=
{
12
}
color
=
"currentColor"
/>
}
onRemove
=
{
helpers
.
onRemove
}
>
{
item
.
label
}
</Chip
>
)
}
inputWidth
=
{
400
}
/>
</Block
>
)
}
Select-Like Behavior
editable={false}), so it can't be typed into or filtered. Tapping opens the full option list (filter={() => true}) and the value is chosen from it.Loading demo…
import
{
useState
}
from
'react'
import
{
AutoComplete
,
Block
}
from
'@platform-blocks/react-ui-library'
;
import
type
{
AutoCompleteOption
}
from
'@platform-blocks/react-ui-library'
;
import
{
countries
}
from
'../data'
export
function
Demo
(
)
{
const
[
inputValue
,
setInputValue
]
=
useState
(
''
)
const
[
selectedCountry
,
setSelectedCountry
]
=
useState
<AutoCompleteOption
|
null
>
(
null
)
return
(
<Block
w
=
{
400
}
>
<AutoComplete
label
=
"Country"
placeholder
=
"Select a country..."
data
=
{
countries
}
value
=
{
inputValue
}
onChangeText
=
{
(
value
)
=>
{
setInputValue
(
value
)
if
(
!
value
)
setSelectedCountry
(
null
)
}
}
onSelect
=
{
(
item
)
=>
{
setSelectedCountry
(
item
)
setInputValue
(
item
.
label
)
}
}
minSearchLength
=
{
0
}
maxSuggestions
=
{
countries
.
length
}
editable
=
{
false
}
caretHidden
filter
=
{
(
)
=>
true
}
highlightMatches
=
{
false
}
fullWidth
/>
</Block
>
)
}
Async auto-complete
Loading demo…
import
{
useState
}
from
'react'
import
{
AutoComplete
,
Block
}
from
'@platform-blocks/react-ui-library'
;
import
type
{
AutoCompleteOption
}
from
'@platform-blocks/react-ui-library'
;
import
{
programmingLanguages
}
from
'../data'
const
searchLanguages
=
async
(
query
:
string
)
:
Promise
<AutoCompleteOption
[
]
>
=>
{
await
new
Promise
(
(
resolve
)
=>
setTimeout
(
resolve
,
400
)
)
const
normalized
=
query
.
toLowerCase
(
)
return
programmingLanguages
.
filter
(
(
language
)
=>
language
.
label
.
toLowerCase
(
)
.
includes
(
normalized
)
,
)
}
export
function
Demo
(
)
{
const
[
inputValue
,
setInputValue
]
=
useState
(
''
)
const
[
selectedLanguage
,
setSelectedLanguage
]
=
useState
<AutoCompleteOption
|
null
>
(
null
)
return
(
<Block
w
=
{
400
}
>
<AutoComplete
label
=
"Search programming languages"
placeholder
=
"Start typing..."
onSearch
=
{
searchLanguages
}
value
=
{
inputValue
}
onChangeText
=
{
(
value
)
=>
{
setInputValue
(
value
)
if
(
!
value
)
setSelectedLanguage
(
null
)
}
}
onSelect
=
{
(
item
)
=>
{
setSelectedLanguage
(
item
)
setInputValue
(
item
.
label
)
}
}
minSearchLength
=
{
2
}
searchDelay
=
{
300
}
highlightMatches
fullWidth
/>
</Block
>
)
}
Free Solo
Loading demo…
import
{
useState
}
from
'react'
import
{
AutoComplete
,
Block
,
Text
}
from
'@platform-blocks/react-ui-library'
import
{
fruits
}
from
'../data'
export
function
Demo
(
)
{
const
[
value
,
setValue
]
=
useState
(
''
)
return
(
<Block
w
=
{
400
}
>
<AutoComplete
label
=
"Favorite fruit"
placeholder
=
"Type anything..."
data
=
{
fruits
}
value
=
{
value
}
onChangeText
=
{
setValue
}
onSelect
=
{
(
item
)
=>
setValue
(
item
.
label
)
}
freeSolo
minSearchLength
=
{
0
}
fullWidth
/>
<Text
size
=
"xs"
color
=
"secondary"
>
Current
value
:
{
value
||
'(empty)'
}
</Text
>
</Block
>
)
}
Free Solo (multi-select)
Loading demo…
import
{
useState
}
from
'react'
import
{
AutoComplete
,
Block
,
Chip
,
Icon
,
Text
}
from
'@platform-blocks/react-ui-library'
;
import
type
{
AutoCompleteOption
}
from
'@platform-blocks/react-ui-library'
;
import
{
fruits
}
from
'../data'
export
function
Demo
(
)
{
const
[
inputValue
,
setInputValue
]
=
useState
(
''
)
const
[
selected
,
setSelected
]
=
useState
<AutoCompleteOption
[
]
>
(
[
]
)
const
handleToggle
=
(
option
:
AutoCompleteOption
)
=>
{
const
isSelected
=
selected
.
some
(
(
item
)
=>
item
.
value
===
option
.
value
)
setSelected
(
(
current
)
=>
isSelected
?
current
.
filter
(
(
item
)
=>
item
.
value
!==
option
.
value
)
:
[
.
.
.
current
,
option
]
,
)
}
return
(
<Block
w
=
{
400
}
>
<AutoComplete
label
=
"Favorite fruits"
placeholder
=
"Type a fruit and press Enter..."
data
=
{
fruits
}
value
=
{
inputValue
}
onChangeText
=
{
setInputValue
}
onSelect
=
{
handleToggle
}
freeSolo
multiSelect
selectedValues
=
{
selected
}
minSearchLength
=
{
0
}
clearable
onClear
=
{
(
)
=>
{
setSelected
(
[
]
)
setInputValue
(
''
)
}
}
selectedValuesContainerStyle
=
{
{
flexWrap
:
'wrap'
,
gap
:
6
}
}
renderSelectedValue
=
{
(
item
,
_index
,
helpers
)
=>
(
<Chip
key
=
{
item
.
value
}
size
=
"sm"
variant
=
"light"
color
=
"primary"
endIcon
=
{
<Icon
name
=
"x"
size
=
{
8
}
color
=
"currentColor"
/>
}
onRemove
=
{
helpers
.
onRemove
}
>
{
item
.
label
}
</Chip
>
)
}
/>
</Block
>
)
}
Grouped suggestions
Loading demo…
import
{
useState
}
from
'react'
import
{
AutoComplete
,
Block
}
from
'@platform-blocks/react-ui-library'
;
import
type
{
AutoCompleteOption
}
from
'@platform-blocks/react-ui-library'
;
import
{
groupedCountries
}
from
'../data'
export
function
Demo
(
)
{
const
[
value
,
setValue
]
=
useState
(
''
)
const
[
selectedCountry
,
setSelectedCountry
]
=
useState
<AutoCompleteOption
|
null
>
(
null
)
return
(
<Block
w
=
{
400
}
>
<AutoComplete
label
=
"Search countries"
placeholder
=
"Search for a country..."
data
=
{
groupedCountries
}
value
=
{
value
}
onChangeText
=
{
(
next
)
=>
{
setValue
(
next
)
if
(
!
next
)
setSelectedCountry
(
null
)
}
}
onSelect
=
{
(
item
)
=>
{
setSelectedCountry
(
item
)
setValue
(
item
.
label
)
}
}
minSearchLength
=
{
1
}
highlightMatches
fullWidth
/>
</Block
>
)
}
Rich Content
Loading demo…
import
{
useState
}
from
'react'
import
{
AutoComplete
,
Block
,
Column
,
Icon
,
MenuItemButton
,
Row
,
Text
}
from
'@platform-blocks/react-ui-library'
interface
RichSportOption
{
label
:
string
;
value
:
string
;
emoji
:
string
;
color
:
string
;
price
:
number
;
// Avg ticket price
duration
:
string
;
// Typical game length / format
}
// The option colors are plain 6-digit hex, so an 8-digit suffix gives a tint
// that works on both web and native without a color library.
const
tint
=
(
hex
:
string
,
alpha
:
string
)
=>
`${hex}${alpha}`
export
function
Demo
(
)
{
const
[
value
,
setValue
]
=
useState
(
''
)
const
[
selectedSport
,
setSelectedSport
]
=
useState
<RichSportOption
|
null
>
(
null
)
const
richSportData
:
RichSportOption
[
]
=
[
{
label
:
'Soccer'
,
value
:
'soccer'
,
emoji
:
'⚽'
,
color
:
'#22c55e'
,
price
:
75.5
,
duration
:
'90 min'
}
,
{
label
:
'Basketball'
,
value
:
'basketball'
,
emoji
:
'🏀'
,
color
:
'#f97316'
,
price
:
120.0
,
duration
:
'48 min'
}
,
{
label
:
'Football'
,
value
:
'football'
,
emoji
:
'🏈'
,
color
:
'#92400e'
,
price
:
180.0
,
duration
:
'60 min'
}
,
{
label
:
'Volleyball'
,
value
:
'volleyball'
,
emoji
:
'🏐'
,
color
:
'#fbbf24'
,
price
:
60.0
,
duration
:
'Best of 5'
}
,
{
label
:
'Baseball'
,
value
:
'baseball'
,
emoji
:
'⚾'
,
color
:
'#ef4444'
,
price
:
85.0
,
duration
:
'9 innings'
}
,
{
label
:
'Golf'
,
value
:
'golf'
,
emoji
:
'⛳'
,
color
:
'#15803d'
,
price
:
110.0
,
duration
:
'4 hrs'
}
,
]
;
// Colored emoji tile — carries the option color instead of a loose dot, and
// doubles as the leading media for both the dropdown row and the input value.
const
renderTile
=
(
sport
:
RichSportOption
,
size
:
number
)
=>
(
<Block
w
=
{
size
}
h
=
{
size
}
radius
=
"lg"
align
=
"center"
justify
=
"center"
bg
=
{
tint
(
sport
.
color
,
'26'
)
}
borderWidth
=
{
1
}
borderColor
=
{
tint
(
sport
.
color
,
'59'
)
}
>
<Text
size
=
{
size
>=
40
?
'xl'
:
'md'
}
>
{
sport
.
emoji
}
</Text
>
</Block
>
)
return
(
<Block
w
=
{
400
}
>
<AutoComplete
label
=
"Search sports"
placeholder
=
"Search sports with rich details..."
data
=
{
richSportData
}
value
=
{
value
}
clearable
// Drop focus after selecting so the rich value overlay shows immediately.
refocusAfterSelect
=
{
false
}
onChangeText
=
{
(
next
)
=>
{
setValue
(
next
)
if
(
!
next
)
setSelectedSport
(
null
)
}
}
onSelect
=
{
(
item
)
=>
{
const
sport
=
item as
RichSportOption
setSelectedSport
(
sport
)
setValue
(
sport
.
label
)
}
}
// Media / title+meta / trailing price — the standard three-slot list row,
// so the eye scans names down the left and prices down the right.
renderItem
=
{
(
item
,
index
,
helpers
)
=>
{
const
sport
=
item as
RichSportOption
const
isChosen
=
selectedSport
?
.
value
===
sport
.
value
return
(
<MenuItemButton
key
=
{
sport
.
value
}
rounded
=
{
false
}
compact
fullWidth
active
=
{
helpers
?
.
isHighlighted
||
isChosen
}
onPress
=
{
(
)
=>
helpers
?
.
onSelect
?
.
(
sport
)
}
style
=
{
{
alignItems
:
'stretch'
,
gap
:
0
}
}
>
<Row
align
=
"center"
gap
=
"md"
px
=
"md"
py
=
"sm"
fullWidth
>
{
renderTile
(
sport
,
40
)
}
<Column
grow
=
{
1
}
gap
=
"xs"
>
<Text
size
=
"sm"
weight
=
"semibold"
numberOfLines
=
{
1
}
>
{
sport
.
label
}
</Text
>
<Text
size
=
"xs"
color
=
"secondary"
numberOfLines
=
{
1
}
>
{
sport
.
duration
}
</Text
>
</Column
>
<Column
align
=
"flex-end"
gap
=
"xs"
>
<Text
size
=
"sm"
weight
=
"semibold"
>
$
{
sport
.
price
.
toFixed
(
2
)
}
</Text
>
<Text
size
=
"xs"
color
=
"secondary"
>
avg ticket
</Text
>
</Column
>
{
isChosen
?
(
<Icon
name
=
"check"
size
=
{
16
}
stroke
=
{
3
}
color
=
{
sport
.
color
}
/>
)
:
(
<Block
w
=
{
16
}
/>
)
}
</Row
>
</MenuItemButton
>
)
}
}
// Chosen sport inside the input box — same tile, single line so it fits
// the field height.
renderValue
=
{
(
item
)
=>
{
const
sport
=
item as
RichSportOption
return
(
<Row
align
=
"center"
gap
=
"sm"
grow
=
{
1
}
>
{
renderTile
(
sport
,
24
)
}
<Text
size
=
"sm"
weight
=
"semibold"
>
{
sport
.
label
}
</Text
>
<Text
size
=
"xs"
color
=
"secondary"
>
{
sport
.
duration
}
</Text
>
<Block
grow
=
{
1
}
/>
<Text
size
=
"sm"
weight
=
"semibold"
>
$
{
sport
.
price
.
toFixed
(
2
)
}
</Text
>
</Row
>
)
}
}
minSearchLength
=
{
1
}
fullWidth
/>
</Block
>
)
}
Highlight colours
highlightMatches bolds and tints the part of each suggestion that matches what you typed. That tint is derived from theme.colors.primary by default; pass highlightColor (and optionally highlightBackgroundColor) to override it — here with shades from theme.colors.highlight. Pick a swatch while the menu is open to see the match repaint.Loading demo…
import
{
useState
}
from
'react'
import
{
AutoComplete
,
Block
,
ColorSwatch
,
Row
,
Text
,
useTheme
}
from
'@platform-blocks/react-ui-library'
const
sampleData
=
[
{
label
:
'Apple'
,
value
:
'apple'
,
description
:
'A red or green fruit'
}
,
{
label
:
'Banana'
,
value
:
'banana'
,
description
:
'A yellow curved fruit'
}
,
{
label
:
'Cherry'
,
value
:
'cherry'
,
description
:
'A small red fruit'
}
,
{
label
:
'Date'
,
value
:
'date'
,
description
:
'A sweet brown fruit'
}
,
{
label
:
'Elderberry'
,
value
:
'elderberry'
,
description
:
'A dark purple berry'
}
,
{
label
:
'Fig'
,
value
:
'fig'
,
description
:
'A purple or green fruit'
}
,
{
label
:
'Grape'
,
value
:
'grape'
,
description
:
'Clusters of small berries'
}
,
{
label
:
'Honeydew'
,
value
:
'honeydew'
,
description
:
'A sweet green melon'
}
,
]
export
function
Demo
(
)
{
const
theme
=
useTheme
(
)
const
isDark
=
theme
.
colorScheme
===
'dark'
// The matched substring sits on the menu surface, so only the shades with
// enough contrast against it are offered: dark end of the ramp on light
// surfaces, light end on dark ones.
const
ramp
=
theme
.
colors
.
highlight
??
[
]
const
shadeIndices
=
isDark
?
[
3
,
4
,
5
,
6
]
:
[
6
,
7
,
8
,
9
]
const
swatches
=
shadeIndices
.
map
(
index
=>
ramp
[
index
]
)
.
filter
(
Boolean
)
// `undefined` = no override, i.e. the primary-derived default AutoComplete
// uses when `highlightColor` is omitted.
const
[
highlightColor
,
setHighlightColor
]
=
useState
<
string
|
undefined
>
(
undefined
)
const
tintIndex
=
isDark
?
8
:
1
return
(
<Block
w
=
{
400
}
gap
=
"md"
>
<AutoComplete
label
=
"Search fruits"
placeholder
=
"Type to search fruits..."
data
=
{
sampleData
}
highlightMatches
highlightColor
=
{
highlightColor
}
highlightBackgroundColor
=
{
highlightColor
?
ramp
[
tintIndex
]
:
undefined
}
minSearchLength
=
{
0
}
fullWidth
/>
<Block
gap
=
"xs"
>
<Text
size
=
"sm"
weight
=
"semibold"
>
Match
colour
</Text
>
<Row
gap
=
"sm"
align
=
"center"
>
<ColorSwatch
color
=
{
theme
.
colors
.
primary
[
isDark
?
5
:
6
]
}
selected
=
{
highlightColor
===
undefined
}
onPress
=
{
(
)
=>
setHighlightColor
(
undefined
)
}
accessibilityLabel
=
"Theme default highlight colour"
/>
{
swatches
.
map
(
color
=>
(
<ColorSwatch
key
=
{
color
}
color
=
{
color
}
selected
=
{
highlightColor
===
color
}
onPress
=
{
(
)
=>
setHighlightColor
(
color
)
}
accessibilityLabel
=
{
`Highlight colour ${color}`
}
/>
)
)
}
</Row
>
<Text
size
=
"xs"
color
=
"secondary"
>
Type
a letter or two
,
then pick a swatch
.
The
first is the
default
(
primary ramp
)
;
the rest come
from
`theme.colors.highlight`
,
paired
with a soft tint
from
the same ramp as the match background
.
</Text
>
</Block
>
</Block
>
)
}
</>
react-ui-library
100+ accessible, themeable components that work seamlessly across iOS, Android, and Web.
A Platform Blocks product.
Quick Links
Resources