Floating UI
The API lets you easily integrate with a positioning library like Floating UI, so you can fine-tune the position of the autocomplete list.
Below are two examples of integration: one for autocomplete and one for a dropdown.
info
These examples are set up to make the list take up as much available height as possible within the viewport. Try opening the list and scrolling the page up and down to observe how the list always perfectly fits within the page.
- Autocomplete.tsx
- Dropdown.tsx
- data/states.ts
import { useState } from 'react';
import { useCombobox, autocomplete } from '@szhsin/react-autocomplete';
import { useFloating, autoUpdate, size } from '@floating-ui/react-dom';
import STATES from '../../data/states';
const Autocomplete = () => {
const [value, setValue] = useState<string>();
const [selected, setSelected] = useState<string>();
const items = value
? STATES.filter((item) => item.toLowerCase().includes(value.toLowerCase()))
: STATES;
const { refs, floatingStyles } = useFloating<HTMLInputElement>({
placement: 'bottom-start',
whileElementsMounted: autoUpdate,
middleware: [
size({
padding: 16,
apply({ availableHeight, elements }) {
// This allows the autocomplete list to take up all the available height.
elements.floating.style.maxHeight = `${availableHeight}px`;
}
})
]
});
const {
getFocusCaptureProps,
getLabelProps,
getInputProps,
getClearProps,
getToggleProps,
getListProps,
getItemProps,
open,
focusIndex,
isInputEmpty
} = useCombobox({
// Provide the `inputRef` prop with the reference returned from the `useFloating` hook
inputRef: refs.reference,
items,
value,
onChange: setValue,
selected,
onSelectChange: setSelected,
feature: autocomplete({ select: true })
});
return (
<div>
<label {...getLabelProps()} {...getFocusCaptureProps()}>
State
</label>
<div>
<input
placeholder="Select or type..."
{...getInputProps()}
// Set the reference element to the input field,
// which should always come after {...getInputProps()}
ref={refs.setReference}
/>
{!isInputEmpty && <button {...getClearProps()}>X</button>}
<button {...getToggleProps()}>{open ? '↑' : '↓'}</button>
</div>
{open && (
<ul
// Set the floating element to the autocomplete list
ref={refs.setFloating}
{...getListProps()}
style={{
listStyle: 'none',
color: '#000',
background: '#fff',
margin: 0,
padding: 0,
overflow: 'auto',
// Apply floating positioning styles to the autocomplete list
...floatingStyles
}}
>
{items.length ? (
items.map((item, index) => (
<li
style={{
background: focusIndex === index ? '#ddd' : 'none',
textDecoration: selected === item ? 'underline' : 'none'
}}
key={item}
{...getItemProps({ item, index })}
>
{item}
</li>
))
) : (
<li>No options</li>
)}
</ul>
)}
</div>
);
};
export default Autocomplete;
import { useState, useRef } from 'react';
import { useCombobox, dropdown } from '@szhsin/react-autocomplete';
import { useFloating, autoUpdate, size } from '@floating-ui/react-dom';
import STATES from '../../data/states';
const Dropdown = () => {
const listRef = useRef<HTMLUListElement>(null);
const [value, setValue] = useState<string>();
const [selected, setSelected] = useState<string>();
const items = value
? STATES.filter((item) => item.toLowerCase().includes(value.toLowerCase()))
: STATES;
const { refs, floatingStyles } = useFloating<HTMLButtonElement>({
placement: 'bottom-start',
whileElementsMounted: autoUpdate,
middleware: [
size({
padding: 16,
apply({ availableHeight }) {
// This allows the dropdown list to take up all the available height.
if (listRef.current && inputRef.current) {
listRef.current.style.maxHeight = `${availableHeight - inputRef.current.getBoundingClientRect().height}px`;
}
}
})
]
});
const {
getInputProps,
getClearProps,
getToggleProps,
getListProps,
getItemProps,
open,
focusIndex,
isInputEmpty,
inputRef
} = useCombobox({
items,
value,
onChange: setValue,
selected,
onSelectChange: setSelected,
feature: dropdown({
// Provide the `toggleRef` prop with the reference returned from the `useFloating` hook
toggleRef: refs.reference
})
});
return (
<div>
<button
{...getToggleProps()}
// Set the reference element to the toggle button,
// which should always come after {...getToggleProps()}
ref={refs.setReference}
>
{selected || 'Select a state'}
</button>
{open && (
<div
{...getListProps()}
// Set the floating element to the dropdown list
ref={refs.setFloating}
style={{
color: '#000',
background: '#fff',
// Apply floating positioning styles to the dropdown list
...floatingStyles
}}
>
<div>
<input placeholder="Type to search..." {...getInputProps()} />
{!isInputEmpty && <button {...getClearProps()}>X</button>}
</div>
<ul
ref={listRef}
style={{
overflow: 'auto',
listStyle: 'none',
margin: 0,
padding: 0
}}
>
{items.length ? (
items.map((item, index) => (
<li
style={{
background: focusIndex === index ? '#ddd' : 'none',
textDecoration: selected === item ? 'underline' : 'none'
}}
key={item}
{...getItemProps({ item, index })}
>
{item}
</li>
))
) : (
<li>No options</li>
)}
</ul>
</div>
)}
</div>
);
};
export default Dropdown;
// https://en.wikipedia.org/wiki/List_of_states_and_territories_of_the_United_States#States
export default [
'Alabama',
'Alaska',
'Arizona',
'Arkansas',
'California',
'Colorado',
'Connecticut',
'Delaware',
'Florida',
'Georgia',
'Hawaii',
'Idaho',
'Illinois',
'Indiana',
'Iowa',
'Kansas',
'Kentucky',
'Louisiana',
'Maine',
'Maryland',
'Massachusetts',
'Michigan',
'Minnesota',
'Mississippi',
'Missouri',
'Montana',
'Nebraska',
'Nevada',
'New Hampshire',
'New Jersey',
'New Mexico',
'New York',
'North Carolina',
'North Dakota',
'Ohio',
'Oklahoma',
'Oregon',
'Pennsylvania',
'Rhode Island',
'South Carolina',
'South Dakota',
'Tennessee',
'Texas',
'Utah',
'Vermont',
'Virginia',
'Washington',
'West Virginia',
'Wisconsin',
'Wyoming'
];