Combobox active selection on input change #1851
Replies: 2 comments 3 replies
-
This is exactly what I’m looking for as well. We are using a fairly long list of options, so when the user types, the selected item could be pretty far down the list (below the fold). When the user types something and hits the down arrow to navigate the options, the active item starts from the selected item, jumping the view way down in a very unexpected manner. To go along with, or support, this, is there any way to manually set the active element? Or to automatically set the active element to the first item in the list whenever the options change, regardless of what is selected? |
Beta Was this translation helpful? Give feedback.
-
I just ran into this issue also. I came up with a good enough solution, but agree since 'active' is a pseudo focus state and we have no means of controlling it programatically, its pretty annoying. It looks especially janky when you're using the combobox to search and the active item is being moved around as relevancy changes. My hacky solution consisted of setting a boolean state to true when the input was dirty, using this to disable every item except the first item - so internally it will make the only available item active, then using an effect to immediately reenable everything when the active item is correct. This produces some flicker that can be fixed by passing through the dirty flag to the items and optimistically rendering as if the active has already been moved to the first item: Within your Combobox component: const items: Item[]; // Your Items
const [moveFocus, setMoveFocus] = useState(false); // Dirty flag
...
<Combobox
onChange={(item: Item) => {
if (moveFocus && firstMatch !== item) {
return; // Ignore selection if we're currently moving focus, this avoids mishaps
}
item.handler(); // Do whatever you would normally do on selection
}}
>
{({ activeOption }) => (
<>
<ResetMoveFocus
setMoveFocus={setMoveFocus}
shouldReset={firstMatch === activeOption && moveFocus}
/>
...
<Combobox.Input
onChange={(event) => {
setQuery(event.target.value); // Update your query
setMoveFocus(true); // Mark it as dirty
}}
/>
...
<Combobox.Options>
{items.map((item, idx) => (// Pass through some things to your items
<Item
item={item}
first={idx === 0}
moveFocus={moveFocus}
/>
))}
</Combobox.Options>
</>
The reset move focus is just a component that triggers the reset effect. you can put the effect somewhere else within the combobox if there is somewhere appropriate. const ResetMoveFocus = (props: { setMoveFocus: (x: boolean) => void; shouldReset: boolean }) => {
const { setMoveFocus, shouldReset } = props;
useEffect(() => {
if (shouldReset) {
setMoveFocus(false);
}
}, [shouldReset]);
return <></>;
}; Then within your item (Combobox.Item) components you'll need to change the condition to render the active state, so you dont get visual flicker: const { item, match, first, moveFocus } = props;
....
<Combobox.Option
value={item}
className={({ focus }) =>
classNames(
"some styles go here",
((!moveFocus && focus) || (moveFocus && first)) && "bg-negative", // your active style
)
}
disabled={moveFocus && !first}
> I found this solution in anger The only remaining jank i've experienced is that key bindings can be slow to be re-enabled but seem to sort themselves out. |
Beta Was this translation helpful? Give feedback.
-
The current behavior of the headlessui combobox seems to be that the selected value is set to active (gray background in a styled option for example) whenever the options dialog is open. This can change if a user hovers over different options. If a user starts typing into the input box (without hovering over other options), the selected option stays as the active value. Is it possible to change the active value to the first element in the options list if a user is typing into the input?
Beta Was this translation helpful? Give feedback.
All reactions