tailwindlabs / headlessui

Completely unstyled, fully accessible UI components, designed to integrate beautifully with Tailwind CSS.
https://headlessui.com
MIT License
25.81k stars 1.07k forks source link

[Combobox] Option to disable focus after a selection #3442

Closed knoefel closed 3 weeks ago

knoefel commented 1 month ago

What package within Headless UI are you using?

@headlessui/react

What version of that package are you using?

2.1.3

What browser are you using?

Chrome

Reproduction URL

Check official example in docs: https://headlessui.com/react/combobox#combobox

Describe your issue

It should be possible to optionally disable focusing the Combobox Input element after selecting an option. Right now you have to use a setTimeout hack to accomplish this. See this discussion for more infos: #2795

RobinMalfait commented 3 weeks ago

Hey!

Thanks for the suggestion. I like to think about the Combobox as-if it's a normal <input> component that just happens to have a popup attached with suggestions to make the user experience even better.

If you think about it as a normal <input>, then the focus also always stays inside of the input field. This is because we don't know when you are "done".

You could of course argue that when you actively make a selection in a Combobox, that it means that you are "done". But you don't really know that, because the user could have clicked the wrong option and have to make a change to correct for this. Aka they were not done yet. You can see this behavior in the native <input type="checkbox"> as well. If you explicitly make a selection (e.g.: check a checkbox), the focus is still on the <input type="checkbox"> element, the browser doesn't know that you were done or not.

Here is another question: Let's say you have a form with multiple Combobox components, and you are done with the first one. How do you want to go to the next one? Now imagine that you are using just your keyboard.

If you explicitly blur the ComboboxInput and you don't move focus to a new element, then the focus will be on the document.body, which means that you have lost your position in the form and you have to start over again. (Assuming you use your keyboard to tab through the form)

If we expose a feature for this, then we very likely will require an element to move the focus to, to ensure that the user is still in the form and a reasonable position.

E.g.:

// ! Just an example, not real code
<Combobox moveFocusOnCloseTo={nextFormElementRef} />

But, if you still want to blur the ComboboxInput, there is no need for (hacky) setTimeout solutions. You can use the existing onChange (called every time the value changes) or even onClose (called when the input actually closes) callbacks if you wish to remove focus from the ComboboxInput. See: https://headlessui.com/react/combobox#combobox

Using the onClose, means that the code from above, could look like this instead: E.g.:

<Combobox onClose={() => nextFormElementRef.current.focus()} />

// or, when tabbing using the keyboard doesn't matter:

<Combobox onClose={() => inputRef.current.blur()} />

So I don't think there is a lot of benefit to a dedicated prop/feature for this, if you use the existing onClose or onChange callbacks.

If this doesn't work for your use case, feel free to share a minimal reproduction repo so we can take a look.

Hope this helps!

knoefel commented 3 weeks ago

Hi @RobinMalfait,

First of all, thank you very much for the detailed response. Perhaps to clarify my use case a bit more: I have two fields. The first is a <Combobox /> component, and the second is an input field that should be focused as soon as the user makes a selection in the combobox.

My initial idea was to handle this in the onChange handler of the combobox, but unfortunately, this didn’t work. Even your suggestion to use the onClose handler didn’t change anything. It was only when I wrapped the focus call in a setTimeout that the second input field got focused, but this worked only in Chrome. In Safari, this approach also doesn’t work. I’ve created a small minimal reproduction repo via CodeSandbox to illustrate the issue. The first set of fields doesn’t work (red background), while the second set (green background) works at least in Chrome. However, if you open it in Safari, neither version works.

https://codesandbox.io/p/sandbox/kind-mclaren-wqx2mk