Skip to content

Commit 51acc1b

Browse files
authored
Open Menu and Listbox on mousedown (#3689)
This is a small behavioral change, but this PR will change when the `Menu` and `Listbox` components open. This PR will now open the `Menu` and `Listbox` components on `mousedown` instead of `click`. This will make it feel more responsive and faster to the user. This is also how macOS for example opens menu-like components on the OS level. This is also how the native `<select>` (at least on macOS) works.
1 parent 9685af7 commit 51acc1b

File tree

5 files changed

+9
-14
lines changed

5 files changed

+9
-14
lines changed

packages/@headlessui-react/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
- Improve `Menu` component performance ([#3685](https://github.com/tailwindlabs/headlessui/pull/3685))
1313
- Improve `Listbox` component performance ([#3688](https://github.com/tailwindlabs/headlessui/pull/3688))
14+
- Open `Menu` and `Listbox` on `mousedown` ([#3689](https://github.com/tailwindlabs/headlessui/pull/3689))
1415

1516
## [2.2.1] - 2025-04-04
1617

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

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3963,11 +3963,10 @@ describe('Mouse interactions', () => {
39633963
it(
39643964
'should be possible to click outside of the listbox, on an element which is within a focusable element, which closes the listbox',
39653965
suppressConsoleLogs(async () => {
3966-
let focusFn = jest.fn()
39673966
render(
39683967
<div>
39693968
<Listbox value={undefined} onChange={(x) => console.log(x)}>
3970-
<Listbox.Button onFocus={focusFn}>Trigger</Listbox.Button>
3969+
<Listbox.Button>Trigger</Listbox.Button>
39713970
<Listbox.Options>
39723971
<Listbox.Option value="alice">alice</Listbox.Option>
39733972
<Listbox.Option value="bob">bob</Listbox.Option>
@@ -3995,9 +3994,6 @@ describe('Mouse interactions', () => {
39953994

39963995
// Ensure the outside button is focused
39973996
assertActiveElement(document.getElementById('btn'))
3998-
3999-
// Ensure that the focus button only got focus once (first click)
4000-
expect(focusFn).toHaveBeenCalledTimes(1)
40013997
})
40023998
)
40033999

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,8 @@ function ButtonFn<TTag extends ElementType = typeof DEFAULT_BUTTON_TAG>(
393393
}
394394
})
395395

396-
let handleClick = useEvent((event: ReactMouseEvent) => {
396+
let handleMouseDown = useEvent((event: ReactMouseEvent) => {
397+
if (event.button !== 0) return // Only handle left clicks
397398
if (isDisabledReactIssue7711(event.currentTarget)) return event.preventDefault()
398399
if (machine.state.listboxState === ListboxStates.Open) {
399400
flushSync(() => machine.actions.closeListbox())
@@ -450,7 +451,7 @@ function ButtonFn<TTag extends ElementType = typeof DEFAULT_BUTTON_TAG>(
450451
onKeyDown: handleKeyDown,
451452
onKeyUp: handleKeyUp,
452453
onKeyPress: handleKeyPress,
453-
onClick: handleClick,
454+
onMouseDown: handleMouseDown,
454455
},
455456
focusProps,
456457
hoverProps,

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

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3077,11 +3077,10 @@ describe('Mouse interactions', () => {
30773077
it(
30783078
'should be possible to click outside of the menu, on an element which is within a focusable element, which closes the menu',
30793079
suppressConsoleLogs(async () => {
3080-
let focusFn = jest.fn()
30813080
render(
30823081
<div>
30833082
<Menu>
3084-
<Menu.Button onFocus={focusFn}>Trigger</Menu.Button>
3083+
<Menu.Button>Trigger</Menu.Button>
30853084
<Menu.Items>
30863085
<Menu.Item as="a">alice</Menu.Item>
30873086
<Menu.Item as="a">bob</Menu.Item>
@@ -3109,9 +3108,6 @@ describe('Mouse interactions', () => {
31093108

31103109
// Ensure the outside button is focused
31113110
assertActiveElement(document.getElementById('btn'))
3112-
3113-
// Ensure that the focus button only got focus once (first click)
3114-
expect(focusFn).toHaveBeenCalledTimes(1)
31153111
})
31163112
)
31173113

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,8 @@ function ButtonFn<TTag extends ElementType = typeof DEFAULT_BUTTON_TAG>(
229229
state.itemsElement,
230230
])
231231

232-
let handleClick = useEvent((event: ReactMouseEvent) => {
232+
let handleMouseDown = useEvent((event: ReactMouseEvent) => {
233+
if (event.button !== 0) return // Only handle left clicks
233234
if (isDisabledReactIssue7711(event.currentTarget)) return event.preventDefault()
234235
if (disabled) return
235236
if (menuState === MenuState.Open) {
@@ -273,7 +274,7 @@ function ButtonFn<TTag extends ElementType = typeof DEFAULT_BUTTON_TAG>(
273274
autoFocus,
274275
onKeyDown: handleKeyDown,
275276
onKeyUp: handleKeyUp,
276-
onClick: handleClick,
277+
onMouseDown: handleMouseDown,
277278
},
278279
focusProps,
279280
hoverProps,

0 commit comments

Comments
 (0)