Alternatives Autocomplete — Presents a filtered list only when the user has provided input. For extremely large datasets, where browsing the complete list would be inappropriate.
Usage The Combobox
is a controlled component . You "own" the state, which is managed by providing handlers to the component.
NOTE: All handlers must be memoized with React.useCallback()
for performance.
NOTE: Pass the window object to the environment prop in Storybook (or when using inside an iframe) to prevent interaction issues.
Basic The following props are required:
inputValue
— The input value
items
— The array of items for the user to select from
onChange
— Handle changes to the selected item
onInputChange
— Handle changes to the input value
value
— The selected item
const itemData = React . useMemo (
( ) => [
{ label : 'Chocolate' , value : 'chocolate' } ,
{ label : 'Vanilla' , value : 'vanilla' } ,
{ label : 'Strawberry' , value : 'strawberry' } ,
] ,
[ ]
) ;
const [ inputValue , setInputValue ] = useState ( '' ) ;
const [ items , setItems ] = useState ( itemData ) ;
const [ selectedItem , setSelectedItem ] = useState ( null ) ;
const onInputChange = React . useCallback (
( text ) => {
setInputValue ( text ) ;
setItems ( ( ) => {
if ( text ) {
return itemData . filter ( ( { label } ) =>
label . toLowerCase ( ) . includes ( text . toLowerCase ( ) )
) ;
}
return itemData ;
} ) ;
} ,
[ itemData ]
) ;
return (
< Field label = " Basic example " >
< Combobox
placeholder = " Select an item "
inputValue = { inputValue }
items = { items }
onChange = { setSelectedItem }
onInputChange = { onInputChange }
value = { selectedItem }
/>
</ Field >
) ;
Clear Optionally provide an onClear
handler, allowing users to "clear" their selection.
NOTE: Like all handlers, onClear
must be memoized with React.useCallback()
for performance.
const itemData = React . useMemo (
( ) => [
{ label : 'Chocolate' , value : 'chocolate' } ,
{ label : 'Vanilla' , value : 'vanilla' } ,
{ label : 'Strawberry' , value : 'strawberry' } ,
] ,
[ ]
) ;
const [ inputValue , setInputValue ] = useState ( '' ) ;
const [ items , setItems ] = useState ( itemData ) ;
const [ selectedItem , setSelectedItem ] = useState ( null ) ;
const onClear = React . useCallback ( ( ) => {
setInputValue ( '' ) ;
setItems ( itemData ) ;
setSelectedItem ( null ) ;
} , [ itemData ] ) ;
const onInputChange = React . useCallback (
( text ) => {
setInputValue ( text ) ;
setItems ( ( ) => {
if ( text ) {
return itemData . filter ( ( { label } ) =>
label . toLowerCase ( ) . includes ( text . toLowerCase ( ) )
) ;
}
return itemData ;
} ) ;
} ,
[ itemData ]
) ;
return (
< Field label = " Clearable example " >
< Combobox
placeholder = " Select an item to show clear action "
inputValue = { inputValue }
items = { items }
onChange = { setSelectedItem }
onClear = { onClear }
onInputChange = { onInputChange }
value = { selectedItem }
/>
</ Field >
) ;
Data shape By default the Combobox
assumes that items will take the following shape:
type Item = {
label : string ;
value : string | number ;
disabled ? : boolean ;
} ;
However, if your items have a different shape, you can provide "transform" functions to let the Combobox
know how to handle your data:
NOTE: All "transform" functions must be memoized with React.useCallback()
for performance.
const itemData = React . useMemo (
( ) => [
{
name : 'Belgian Chocolate' ,
id : '3746856f-13b1-41f8-810d-1d932342c5dd' ,
score : 9 ,
} ,
{
name : 'Vanilla Bean' ,
id : '57f74719-da2c-4eec-a73a-80ec8f8dea94' ,
score : 4 ,
} ,
{
name : 'Strawberries & Cream' ,
id : '5a66d5a1-66f4-483b-ba9b-59c542f5cca2' ,
score : 7 ,
} ,
] ,
[ ]
) ;
const [ items , setItems ] = useState ( itemData ) ;
const [ selectedItem , setSelectedItem ] = useState ( null ) ;
const [ inputValue , setInputValue ] = useState ( '' ) ;
const itemToDisabled = React . useCallback ( ( item ) => item . score < 5 , [ ] ) ;
const itemToLabel = React . useCallback ( ( item ) => item . name , [ ] ) ;
const itemToValue = React . useCallback ( ( item ) => item . id , [ ] ) ;
const onInputChange = React . useCallback (
( text ) => {
setInputValue ( text ) ;
setItems ( ( ) => {
if ( text ) {
return itemData . filter ( ( { name } ) =>
name . toLowerCase ( ) . includes ( text . toLowerCase ( ) )
) ;
}
return itemData ;
} ) ;
} ,
[ itemData ]
) ;
return (
< Field label = " Data shape example " >
< Combobox
itemToDisabled = { itemToDisabled }
itemToLabel = { itemToLabel }
itemToValue = { itemToValue }
placeholder = "Select"
items = { items }
onInputChange = { onInputChange }
inputValue = { inputValue }
onChange = { setSelectedItem }
value = { selectedItem }
/ >
</ Field >
) ;
Appearance For brevity, search and selection are not implemented in the "appearance" examples.
Disabled When "disabled" the Combobox
will not be interactive and take on a dim appearance.
< Field label = " Disabled example " >
< Combobox disabled inputValue = " Not interactive " items = { [ ] } />
</ Field >
Invalid When the parent Field
has an "invalidMessage" the Combobox
controls will take on a "critical" appearance.
< Field label = " Invalid example " invalidMessage = " Required field " >
< Combobox
inputValue = " "
onInputChange = { ( ) => { } }
onChange = { ( ) => { } }
items = { [
{ label : 'Chocolate' , value : 'chocolate' } ,
{ label : 'Vanilla' , value : 'vanilla' } ,
{ label : 'Strawberry' , value : 'strawberry' } ,
] }
/>
</ Field >
Size The Combobox
suppports a size
property, which influences the input controls and the default item height. For dense interfaces where screen reale state is limited you may want to use the "small" size
.
< Field label = " Small example " >
< Combobox
size = " small "
inputValue = " "
onInputChange = { ( ) => { } }
onChange = { ( ) => { } }
value = { { label : 'Chocolate' , value : 'chocolate' } }
items = { [
{ label : 'Chocolate' , value : 'chocolate' } ,
{ label : 'Vanilla' , value : 'vanilla' } ,
{ label : 'Strawberry' , value : 'strawberry' } ,
] }
/>
</ Field >
Placeholder The text that appears in the form control when it has no value set.
< Field label = " Placeholder example " >
< Combobox
placeholder = " Placeholder text "
inputValue = " "
onInputChange = { ( ) => { } }
onChange = { ( ) => { } }
items = { [
{ label : 'Chocolate' , value : 'chocolate' } ,
{ label : 'Vanilla' , value : 'vanilla' } ,
{ label : 'Strawberry' , value : 'strawberry' } ,
] }
/>
</ Field >
Custom item To display a custom item you can provide an itemRenderer
to the combobox.
NOTE: Should be a pure function (no side-effects), declared outside of the component.
const itemRenderer = React . useCallback ( ( { label } ) => {
return (
< Flex gap = " small " alignItems = " center " >
< UserAvatar name = { label } size = " xsmall " />
< Text > { label } </ Text >
</ Flex >
) ;
} , [ ] ) ;
return (
< Field label = " Custom item example " >
< Combobox
itemRenderer = { itemRenderer }
inputValue = " "
onInputChange = { ( ) => { } }
onChange = { ( ) => { } }
items = { [
{ label : 'Chocolate' , value : 'chocolate' } ,
{ label : 'Vanilla' , value : 'vanilla' } ,
{ label : 'Strawberry' , value : 'strawberry' } ,
] }
/>
</ Field >
) ;
Item height Because the scroll menu is virtualized we need an itemHeight
(in pixels) to safely render the list. You only need to provide an itemHeight
when using a custom itemRenderer
and the height of your items differs from the default item implementation.
const itemRenderer = React . useCallback ( ( { label } ) => {
return (
< Flex gap = " small " alignItems = " center " >
< UserAvatar name = { label } size = " small " />
< Stack >
< Text > { label } </ Text >
< Text weight = " regular " color = " muted " >
Ice cream flavour
</ Text >
</ Stack >
</ Flex >
) ;
} , [ ] ) ;
return (
< Field label = " Item height example " >
< Combobox
itemRenderer = { itemRenderer }
itemHeight = { 48 }
inputValue = ""
onInputChange = { ( ) => { } }
onChange = { ( ) => { } }
items = { [
{ label : 'Chocolate' , value : 'chocolate' } ,
{ label : 'Vanilla' , value : 'vanilla' } ,
{ label : 'Strawberry' , value : 'strawberry' } ,
] }
/ >
</ Field >
) ;
By default, the menu dialog will match the width of the input container. For situations where horizontal real estate is limited this might mean that menu items are clipped. Provide a menuWidth
prop to resolve the issue, accomodating expected item label lengths.
return (
< Box width = { 150 } >
< Field label = " Menu items not cutoff " >
< Combobox
menuWidth = { 200 }
inputValue = ""
onInputChange = { ( ) => { } }
onChange = { ( ) => { } }
items = { [
{ label : 'Yummy Chocolate' , value : 'chocolate' } ,
{ label : 'Sweet Vanilla' , value : 'vanilla' } ,
{ label : 'Amazing Strawberry' , value : 'strawberry' } ,
] }
/ >
</ Field >
</ Box >
) ;
Async The logic for filtering items typically lives on the server rather than the client because it’s impractical to send all possible items over the network. However, when prototyping in Playroom or working with smaller datasets, you may want to perform this filtering on the client instead.
The async examples simulate a server request using the simulateFetch
utility. You should not use this in your application.
Filtering When fetching data from the server, update the loadingState
to keep users informed. While the request is being processed, the loading indicator will let users know that their input has been recognised and that something is happening.
< Combobox loadingState = " loading " />
NOTE: When fetching from the server based on user input you should always implement a constraint technique, like debounce or throttle , to limit the amount of requests.
const simulateFetch = ( ms ) => new Promise ( ( r ) => setTimeout ( r , ms ) ) ;
const exampleItemsFromTheServer = React . useMemo ( ( ) => [
{ label : 'Banana' , value : 'banana' } ,
{ label : 'Blueberry' , value : 'blueberry' } ,
{ label : 'Bubble Gum' , value : 'bubble-gum' } ,
{ label : 'Chocolate' , value : 'chocolate' } ,
{ label : 'Coconut' , value : 'coconut' } ,
{ label : 'Coffee' , value : 'coffee' } ,
{ label : 'Cookie Dough' , value : 'cookie-dough' } ,
{ label : 'Cotton Candy' , value : 'cotton-candy' } ,
{ label : 'Mango' , value : 'mango' } ,
{ label : 'Matcha' , value : 'matcha' } ,
{ label : 'Mint' , value : 'mint' } ,
{ label : 'Oreo' , value : 'oreo' } ,
{ label : 'Peanut Butter' , value : 'peanut-butter' } ,
{ label : 'Pistachio' , value : 'pistachio' } ,
{ label : 'Pumpkin' , value : 'pumpkin' } ,
{ label : 'Salted Caramel' , value : 'salted-caramel' } ,
{ label : 'Strawberry' , value : 'strawberry' } ,
{ label : 'Vanilla' , value : 'vanilla' } ,
] ) ;
const initialList = React . useMemo (
( ) => [
{ label : 'Chocolate' , value : 'chocolate' } ,
{ label : 'Vanilla' , value : 'vanilla' } ,
{ label : 'Strawberry' , value : 'strawberry' } ,
] ,
[ ]
) ;
const [ loadingState , setLoadingState ] = useState ( 'idle' ) ;
const [ inputValue , setInputValue ] = useState ( '' ) ;
const [ items , setItems ] = useState ( initialList ) ;
const [ selectedItem , setSelectedItem ] = useState ( null ) ;
const onInputChange = React . useCallback ( ( text ) => {
setInputValue ( text ) ;
} ) ;
React . useEffect ( ( ) => {
const fetchData = async ( ) => {
if ( ! inputValue ) {
setItems ( initialList ) ;
return ;
}
setLoadingState ( 'loading' ) ;
await simulateFetch ( Math . random ( ) * 1000 ) ;
setItems ( ( ) => {
if ( inputValue ) {
return exampleItemsFromTheServer . filter ( ( { label } ) =>
label . toLowerCase ( ) . includes ( inputValue . toLowerCase ( ) )
) ;
}
return exampleItemsFromTheServer ;
} ) ;
setLoadingState ( 'idle' ) ;
} ;
fetchData ( ) ;
} , [ inputValue , selectedItem ] ) ;
return (
< Field label = " Async filter example " >
< Combobox
placeholder = " Input text to filter "
loadingState = { loadingState }
items = { items }
onInputChange = { onInputChange }
inputValue = { inputValue }
onChange = { setSelectedItem }
value = { selectedItem }
/>
</ Field >
) ;
Paginated data is handled in the Combobox
using "infinite scrolling", a technique for loading more results as the user nears the bottom of a scroll view.
To paginate results you must provide:
canLoadMore
— signal that more content is available
onLoadMore
— called when the user nears the bottom of the menu (if canLoadMore
)
loadingState
— must be kept in-sync to avoid duplicate calls
NOTE: Like all handlers, onLoadMore
must be memoized with React.useCallback()
for performance.
const simulateFetch = ( ms ) => new Promise ( ( r ) => setTimeout ( r , ms ) ) ;
const itemsPerPage = 20 ;
const exampleItemsFromTheServer = React . useMemo (
( ) =>
Array . from ( { length : 99 } ) . map ( ( _ , i ) => ( {
label : ` Item ${ i + 1 } ` ,
value : i ,
} ) ) ,
[ ]
) ;
const onInputChange = React . useCallback (
( text ) => {
const nextState = text ? 'error' : 'idle' ;
setLoadingState ( nextState ) ;
setInputValue ( text ) ;
} ,
[ items ]
) ;
const getMessageText = React . useCallback ( ( ) => {
return 'Search not implemented for this example' ;
} ) ;
const [ loadingState , setLoadingState ] = useState ( 'idle' ) ;
const [ inputValue , setInputValue ] = useState ( '' ) ;
const [ items , setItems ] = useState (
exampleItemsFromTheServer . slice ( 0 , itemsPerPage )
) ;
const [ selectedItem , setSelectedItem ] = useState ( null ) ;
const canLoadMore = items . length < exampleItemsFromTheServer . length ;
const onLoadMore = React . useCallback ( async ( ) => {
setLoadingState ( 'loadingMore' ) ;
await simulateFetch ( Math . random ( ) * 1000 ) ;
setItems ( ( curr ) =>
exampleItemsFromTheServer . slice ( 0 , curr . length + itemsPerPage )
) ;
setLoadingState ( 'idle' ) ;
} ) ;
return (
< Field label = " Async pagination example " >
< Combobox
mode = "paginated"
canLoadMore = { canLoadMore }
onLoadMore = { onLoadMore }
loadingState = { loadingState }
placeholder = "Scroll to load more items"
loadingState = { loadingState }
items = { items }
onInputChange = { onInputChange }
inputValue = { inputValue }
onChange = { setSelectedItem }
value = { selectedItem }
getMessageText = { getMessageText }
/ >
</ Field >
) ;
Advanced The Combobox
is tailored for the most common usage—where possible, simplicity and reduced API surface-area is preferred. There are however features available should you need them.
Actions such as "Create new" can be shown in the footer using the footerItem
prop. The footer item is just a normal item rendered differently (for accessibility reasons) so most of the behavior is left to the consumer.
Below is a sample of how to implement a "Create new" item in the footer.
const itemData = React . useMemo (
( ) => [
{ label : 'Chocolate' , value : 'chocolate' } ,
{ label : 'Vanilla' , value : 'vanilla' } ,
{ label : 'Strawberry' , value : 'strawberry' } ,
{ label : 'Pineapple' , value : 'pineapple' } ,
{ label : 'Mango' , value : 'mango' } ,
{ label : 'Marshmallow' , value : 'marshmallow' } ,
{ label : 'Peach' , value : 'peach' } ,
{ label : 'Watermelon' , value : 'watermelon' } ,
{ label : 'Guava' , value : 'guava' } ,
{ label : 'Custard apple' , value : 'custardapple' } ,
{ label : 'Plum' , value : 'plum' } ,
{ label : 'Rockmelon' , value : 'rockmelon' } ,
] ,
[ ]
) ;
const [ inputValue , setInputValue ] = useState ( '' ) ;
const [ items , setItems ] = useState ( itemData ) ;
const [ selectedItem , setSelectedItem ] = useState ( null ) ;
const onInputChange = React . useCallback (
( text ) => {
setInputValue ( text ) ;
setItems ( ( ) => {
if ( text ) {
return itemData . filter ( ( { label } ) =>
label . toLowerCase ( ) . includes ( text . toLowerCase ( ) )
) ;
}
return itemData ;
} ) ;
} ,
[ itemData ]
) ;
const onClear = React . useCallback ( ( ) => {
setInputValue ( '' ) ;
setItems ( itemData ) ;
setSelectedItem ( null ) ;
} , [ itemData ] ) ;
const handleCreateNewClick = React . useCallback ( ( ) => {
onClear ( ) ;
} , [ onClear , inputValue ] ) ;
const footerItem = React . useMemo ( ( ) => {
if ( selectedItem ) {
return null ;
}
const exactMatchFound = itemData . find (
( item ) => item && item . value . toLowerCase ( ) === inputValue . toLowerCase ( )
) ;
if ( exactMatchFound ) {
return null ;
}
return {
label : '_createNew' ,
value : 'Create new' ,
} ;
} , [ inputValue , itemData , selectedItem ] ) ;
const footerNode = React . useMemo ( ( ) => {
if ( ! inputValue ) {
return < TextLinkButton > Create new item </ TextLinkButton > ;
}
return (
< Flex justifyContent = " space-between " paddingRight = " large " flexGrow = { 1 } >
< Text > { ` " ${ inputValue } " ` } </ Text >
< Text >
< TextLinkButton > Create new </ TextLinkButton >
</ Text >
</ Flex >
) ;
} , [ inputValue ] ) ;
const itemRenderer = React . useCallback (
( item ) => {
return item . value === footerItem ?. value ? footerNode : < > { item . label } </ > ;
} ,
[ footerItem , footerNode ]
) ;
return (
< Field label = " Footer example " >
< Combobox
placeholder = " Select an item "
inputValue = { inputValue }
items = { items }
footerItem = { footerItem }
itemRenderer = { itemRenderer }
onChange = { ( item ) => {
if ( item ?. value === footerItem ?. value ) {
handleCreateNewClick ( ) ;
return ;
}
setSelectedItem ( item ) ;
} }
onInputChange = { onInputChange }
value = { selectedItem }
onClear = { onClear }
/>
</ Field >
) ;
State changes The Combobox
implements Downshift under-the-hood. Both onChange
and onInputChange
will provide "next state" as their second argument. This state is a result of the respective events, and can be used to alter the behaviour of your component.
type ComboboxState = Partial < {
highlightedIndex : number ;
inputValue : string ;
isOpen : boolean ;
selectedItem : Item | null ;
} > ;
In the example below we're checking whether the input value exactly matches the recently selected item. If this is the case, we reset the list of items so they're visible next time the menu is opened.
const itemData = React . useMemo (
( ) => [
{ label : 'Chocolate' , value : 'chocolate' } ,
{ label : 'Vanilla' , value : 'vanilla' } ,
{ label : 'Strawberry' , value : 'strawberry' } ,
] ,
[ ]
) ;
const [ items , setItems ] = useState ( itemData ) ;
const [ selectedItem , setSelectedItem ] = useState ( null ) ;
const [ inputValue , setInputValue ] = useState ( '' ) ;
const onInputChange = React . useCallback (
( text , state ) => {
setInputValue ( text ) ;
setItems ( ( ) => {
if ( state . selectedItem ?. label === text ) {
return itemData ;
}
if ( text ) {
return itemData . filter ( ( { label } ) =>
label . toLowerCase ( ) . includes ( text . toLowerCase ( ) )
) ;
}
return itemData ;
} ) ;
} ,
[ itemData ]
) ;
return (
< Field label = " State changes example " >
< Combobox
placeholder = " Select an item "
items = { items }
onInputChange = { onInputChange }
inputValue = { inputValue }
onChange = { setSelectedItem }
value = { selectedItem }
/>
</ Field >
) ;
State reducer For granular control of the state changing process, you can provide your own reducer . When stateReducer
is called it will receive the previous state
and the actionAndChanges
object. actionAndChanges
contains the change type, which explains why the state is being changed. It also contains the changes proposed that should occur as a consequence of that change type.
NOTE: The stateReducer
should be a pure function (no side-effects), it should be declared it outside of your component.
The TS types you'll need are re-exported from this package as UseCombobox*
. While Balance prefers string literals, Downshift implements an Enum for the change "type", which is exposed as stateChangeTypesEnum
.
In the example below we'll uppercase the input value, and return the new value along with the rest of the changes. For all other state change types, we return the default changes.
const itemData = React . useMemo (
( ) => [
{ label : 'Chocolate' , value : 'chocolate' } ,
{ label : 'Vanilla' , value : 'vanilla' } ,
{ label : 'Strawberry' , value : 'strawberry' } ,
] ,
[ ]
) ;
const [ items , setItems ] = useState ( itemData ) ;
const [ selectedItem , setSelectedItem ] = useState ( null ) ;
const [ inputValue , setInputValue ] = useState ( '' ) ;
const onInputChange = React . useCallback (
( text , allChanges ) => {
setInputValue ( text ) ;
setItems ( ( ) => {
if ( allChanges . selectedItem ?. label === text ) {
return itemData ;
}
if ( text ) {
return itemData . filter ( ( { label } ) =>
label . toLowerCase ( ) . includes ( text . toLowerCase ( ) )
) ;
}
return itemData ;
} ) ;
} ,
[ itemData ]
) ;
const stateReducer = React . useCallback ( ( state , actionAndChanges ) => {
const { type , changes } = actionAndChanges ;
switch ( type ) {
case stateChangeTypesEnum . InputChange :
return {
... changes ,
inputValue : changes . inputValue . toUpperCase ( ) ,
} ;
case stateChangeTypesEnum . ItemClick :
case stateChangeTypesEnum . InputKeyDownEnter :
case stateChangeTypesEnum . InputBlur :
return {
... changes ,
... ( changes . selectedItem && {
inputValue : changes . inputValue . toUpperCase ( ) ,
} ) ,
} ;
default :
return changes ;
}
} , [ ] ) ;
return (
< Field label = " State reducer example " >
< Combobox
placeholder = " Enter text or select an item to see effects "
stateReducer = { stateReducer }
items = { items }
onInputChange = { onInputChange }
inputValue = { inputValue }
onChange = { setSelectedItem }
value = { selectedItem }
/>
</ Field >
) ;
Messages You can customise the message displayed to users when no items are available by providing a getMessageText
callback, which has the signature:
( status : 'error' | 'noResults' , inputValue : string ) => string ;
You can define this function outside of your component, or implement React.useCallback()
to memoize it. Prefer the former where possible.
NOTE: You must account for each status
in your function definition.
const itemData = React . useMemo (
( ) => [
{ label : 'Chocolate' , value : 'chocolate' } ,
{ label : 'Vanilla' , value : 'vanilla' } ,
{ label : 'Strawberry' , value : 'strawberry' } ,
] ,
[ ]
) ;
const [ loadingState , setLoadingState ] = useState ( 'idle' ) ;
const [ inputValue , setInputValue ] = useState ( '' ) ;
const [ items , setItems ] = useState ( itemData ) ;
const [ selectedItem , setSelectedItem ] = useState ( null ) ;
const getMessageText = React . useCallback ( ( status , input ) => {
switch ( status ) {
case 'error' :
return 'Error fetching items, please try again.' ;
case 'noMatches' :
default :
return ` No results matching " ${ input } ". ` ;
}
} ) ;
const onInputChange = React . useCallback (
( text ) => {
setInputValue ( text ) ;
setItems ( ( ) => {
if ( text ) {
return itemData . filter ( ( { label } ) =>
label . toLowerCase ( ) . includes ( text . toLowerCase ( ) )
) ;
}
return itemData ;
} ) ;
} ,
[ itemData ]
) ;
return (
< Stack gap = " large " >
< Fieldset legend = " Loading state " >
< RadioGroup value = { loadingState } onChange = { setLoadingState } >
< Inline gap = " small " >
< Radio value = " idle " > Idle </ Radio >
< Radio value = " error " > Error </ Radio >
</ Inline >
</ RadioGroup >
</ Fieldset >
< Field label = " Custom messages " >
< Combobox
getMessageText = { getMessageText }
loadingState = { loadingState }
placeholder = "Custom messages"
inputValue = { inputValue }
items = { items }
onChange = { setSelectedItem }
onInputChange = { onInputChange }
value = { selectedItem }
/ >
</ Field >
</ Stack >
) ;
The environment prop This prop is only useful if you're rendering the combobox within a different window context from where your JavaScript is running; for example, an iframe or a shadow-root.
Simply pass the window object to the environment.
< Combobox { ... otherProps } environment = { window } />