Skip to content

Commit 0f27e7f

Browse files
Ensure pressing Tab in the ComboboxInput, correctly syncs the input value (#3785)
We prevent syncing the `ComboboxInput` value while you are typing. However, when you press `Tab` there is keyboard event causing the component to be marked as if you are currently typing. But since we are tabbing away from the input we should not be "typing" anymore. This ensures that the `input` can be updated with whatever the current value should be. Fixes: #3738 Fixes: #3595 Fixes: #3537 Closes: #3596 --------- Co-authored-by: Daniil Savitskii <[email protected]>
1 parent e475a82 commit 0f27e7f

File tree

3 files changed

+52
-0
lines changed

3 files changed

+52
-0
lines changed

packages/@headlessui-react/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313
- Ensure `onChange` types are contravariant instead of bivariant ([#3781](https://github.com/tailwindlabs/headlessui/pull/3781))
1414
- Support `<summary>` as a focusable element inside `<details>` ([#3389](https://github.com/tailwindlabs/headlessui/pull/3389))
1515
- Fix `Maximum update depth exceeded` crash when using `transition` prop ([#3782](https://github.com/tailwindlabs/headlessui/pull/3782))
16+
- Ensure pressing `Tab` in the `ComboboxInput`, correctly syncs the input value ([#3785](https://github.com/tailwindlabs/headlessui/pull/3785))
1617

1718
## [2.2.7] - 2025-07-30
1819

packages/@headlessui-react/src/components/combobox/combobox.test.tsx

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3035,6 +3035,56 @@ describe.each([{ virtual: true }, { virtual: false }])(
30353035
assertActiveElement(document.querySelector('#before-combobox'))
30363036
})
30373037
)
3038+
3039+
it(
3040+
'pressing Tab should sync the ComboboxInput value again',
3041+
suppressConsoleLogs(async () => {
3042+
function Example() {
3043+
let [value, setValue] = useState<string | null>(null)
3044+
3045+
return (
3046+
<>
3047+
<MyCombobox
3048+
comboboxProps={{ value, onChange: setValue }}
3049+
inputProps={{
3050+
displayValue: (value: string | null) => value ?? '<cleared>',
3051+
}}
3052+
/>
3053+
<button id="clear" onClick={() => setValue(null)}>
3054+
Clear selection
3055+
</button>
3056+
</>
3057+
)
3058+
}
3059+
3060+
render(<Example />)
3061+
3062+
assertComboboxButton({ state: ComboboxState.InvisibleUnmounted })
3063+
assertComboboxList({ state: ComboboxState.InvisibleUnmounted })
3064+
3065+
// Open combobox
3066+
await click(getComboboxButton())
3067+
3068+
// Select the 2nd option
3069+
await press(Keys.ArrowDown)
3070+
3071+
// Tab to the next DOM node
3072+
await press(Keys.Tab)
3073+
3074+
// Verify it is closed
3075+
assertComboboxButton({ state: ComboboxState.InvisibleUnmounted })
3076+
assertComboboxList({ state: ComboboxState.InvisibleUnmounted })
3077+
3078+
// Verify the selected value was the highlighted one
3079+
expect(getComboboxInput()?.value).toBe('Option B')
3080+
3081+
// Clear the option
3082+
await click(document.querySelector('button#clear') as HTMLButtonElement)
3083+
3084+
// Verify the input value is cleared
3085+
expect(getComboboxInput()?.value).toBe('<cleared>')
3086+
})
3087+
)
30383088
})
30393089

30403090
describe('`Escape` key', () => {

packages/@headlessui-react/src/components/combobox/combobox.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,7 @@ function InputFn<
785785
return machine.actions.closeCombobox()
786786

787787
case Keys.Tab:
788+
machine.actions.setIsTyping(false)
788789
if (machine.state.comboboxState !== ComboboxState.Open) return
789790
if (
790791
data.mode === ValueMode.Single &&

0 commit comments

Comments
 (0)