picker: Fix mouse overrides selected item highlight in searchable dropdown#50827
Conversation
|
Hi, thanks for picking another bug from my wishlist :) I've tested this and noticed that there are still two highlighted items if your mouse pointer is over a picker list item when the picker is opened. After I focus another item using either keyboard or mouse, only one item is highlighted. If I type another character, the second highlight sometimes returns. CleanShot.2026-03-07.at.16.15.01.mp4While testing this PR, I started wondering what the correct behavior actually is for a picker like that. This sent me down a deep rabbit hole: Comparison of pickers in different code editors
I couldn't reproduce that result. In VS Code, I actually see two highlighted items, but the highlight is different for keyboard and mouse navigation, so they're easy to differentiate. Both file and action pickers behave this way: CleanShot.2026-03-07.at.16.28.19.mp4This is similar to how Zed's picker works, except that focus and hover colors are the same, so you can't tell them apart. PhpStorm (IntelliJ), on the other hand, does not seem to have mouse hover states at all, which for me felt weird to use: CleanShot.2026-03-07.at.16.36.40.mp4I've also tested JetBrains Fleet (their experimental editor, now discontinued). Their file picker seems to work the same as in VS Code, except that the mouse hover (and pointer) is not visible at all until you move your mouse. When you move the mouse, hover state works normally, but if you use keyboard arrows, it disappears again. CleanShot.2026-03-07.at.21.47.34.mp4Their action picker behaves differently — it can only have one highlighted item at a time, so the highlight is “shared” between keyboard and mouse: CleanShot.2026-03-07.at.16.56.33.mp4It may seem strange on the screen recording, but in real life you don't use the mouse and arrow keys simultaneously. I believe that this PR would take us in this direction. I've also tested the Raycast picker, which works like the Fleet file picker. The colors it uses for hover and focus are very similar, but it's not confusing, because the hover is not visible unless you move your mouse. CleanShot.2026-03-07.at.21.46.22.mp4TL;DR: every editor is slightly different; the current implementation in Zed is based on VS Code's picker; this PR matches neither VS Code nor IntelliJ; my favorite was the JetBrains Fleet file picker. After playing with all these pickers, the one I like the most is the file picker from Fleet. My ideal picker would behave like this:
This approach is very minimal and keyboard-first (it doesn't distract you with the pointer and a second highlight), which I think is perfect for a code editor. At the same time, using it with a mouse still feels natural. This would also allow us to fix #50698 without introducing additional colors: both hover and focus could still look the same, but because hover wouldn't be visible unless you start using mouse, you would only see one of them when interacting with the picker using keyboard. When using mouse, it's easy to tell them apart because you know where the mouse pointer is. This is what Raycast does and I've never found it confusing. If we went with the option detailed above, and decided to hide the hover state when the mouse is not used, #50698 would be solved without changing colors. Since the current implementation seems to be based on VS Code behavior, I think the simplest fix to #50698 would be to just make hover and focus colors different. Other editors use blue and gray, but I'm not sure whether this would fit Zed's design. Maybe weaker gray for hover would be better? This PR in its current state would change Zed's picker to behave more like the Fleet action picker (which is also a valid choice, but not my favorite). |
|
Hmm we will have to wait for the team to decide on this, lets see when weekends are over !! |
Refines the approach from the previous commit, which used per-picker overlay divs to block hover during keyboard navigation. Instead, make this a first-class framework behavior by teaching is_hovered() about input modality — it now returns false when the last input was a keystroke, and resumes returning true on mouse movement. - Track modality transitions: KeyDown → Keyboard, MouseMove/MouseDown → Mouse - Repaint on modality change so hover state is re-evaluated promptly - Add modality check to tooltip prepaint visibility - Simplify picker: remove overlay-div hack, keep the new on_hover handler Fixes #31865
|
Thanks for digging into this, @OmChillure! the overlay approach was a clever workaround and it got me thinking about where the real fix belongs. I pushed a follow-up commit that moves hover suppression into GPUI itself. The core idea is the same as yours — suppress hover when navigating with the keyboard, restore it when the mouse moves — but instead of per-item overlay divs, we teach This lets us remove the A bonus: tooltips also dismiss on modality shift now, so you don't get stale tooltips hanging around after you start navigating with the keyboard. I'd appreciate your review of my refinements — I want to make sure I'm not missing any edge cases you considered. In particular, let me know if you see any interactions that feel wrong when testing. |
# Conflicts: # crates/gpui/src/window.rs
|
I'm going to go ahead and merge this just to get the benefits, but please offer any feedback you have, and we can always fix forward. |
Thanks @nathansobo ! Moving hover suppression into is_hovered() at the framework level is a much better approach - works across the entire UI without per-component workarounds. Tested on main and it handles all the pickers well and this is also fixed. Though I should have done it like this in my approach !! will keep in mind. |
…pdown (zed-industries#50827) ### Fixes zed-industries#31865 ### Problem When navigating a picker (command palette, file finder, etc.) with the keyboard and then hovering with the mouse, two items would appear highlighted at the same time - one from the keyboard selection ghost_element_selected background and one from the CSS style ghost_element_hover background. This happened because GPUI's CSS hover is applied at paint time independently from the Rust selection state. ### Solution Added a keyboard_navigated: bool flag to [Picker] to track whether the last navigation was via keyboard or mouse. When a keyboard navigation action fires (↑, ↓, Home, End, etc.), `keyboard_navigated` is set to true. A transparent `block_mouse_except_scroll` overlay div is added on top of each list item, preventing the CSS style from firing on `ListItem` children. When the mouse moves over any item, the overlay's `on_mouse_move` handler fires, clears keyboard_navigated, moves the selection to that item, and re-renders - removing all overlays and restoring normal mouse hover behavior. The `on_hover` handler also moves the selection to whichever item the mouse enters, so mouse navigation is fully functional. Clicks work through the overlay since `block_mouse_except_scroll` only affects hover hit-testing, not click events on the parent div. ### Behavior Matches VS Code / IntelliJ — keyboard navigation suppresses hover highlights until the mouse moves, at which point normal hover resumes. Only one item is ever highlighted at a time. ### Video [Screencast from 2026-03-05 19-20-16.webm](https://github.com/user-attachments/assets/7a8a543a-95f3-481f-b59d-171604e5f883) --------- Co-authored-by: Nathan Sobo <nathan@zed.dev>
…pdown (zed-industries#50827) ### Fixes zed-industries#31865 ### Problem When navigating a picker (command palette, file finder, etc.) with the keyboard and then hovering with the mouse, two items would appear highlighted at the same time - one from the keyboard selection ghost_element_selected background and one from the CSS style ghost_element_hover background. This happened because GPUI's CSS hover is applied at paint time independently from the Rust selection state. ### Solution Added a keyboard_navigated: bool flag to [Picker] to track whether the last navigation was via keyboard or mouse. When a keyboard navigation action fires (↑, ↓, Home, End, etc.), `keyboard_navigated` is set to true. A transparent `block_mouse_except_scroll` overlay div is added on top of each list item, preventing the CSS style from firing on `ListItem` children. When the mouse moves over any item, the overlay's `on_mouse_move` handler fires, clears keyboard_navigated, moves the selection to that item, and re-renders - removing all overlays and restoring normal mouse hover behavior. The `on_hover` handler also moves the selection to whichever item the mouse enters, so mouse navigation is fully functional. Clicks work through the overlay since `block_mouse_except_scroll` only affects hover hit-testing, not click events on the parent div. ### Behavior Matches VS Code / IntelliJ — keyboard navigation suppresses hover highlights until the mouse moves, at which point normal hover resumes. Only one item is ever highlighted at a time. ### Video [Screencast from 2026-03-05 19-20-16.webm](https://github.com/user-attachments/assets/7a8a543a-95f3-481f-b59d-171604e5f883) --------- Co-authored-by: Nathan Sobo <nathan@zed.dev>
…pdown (zed-industries#50827) ### Fixes zed-industries#31865 ### Problem When navigating a picker (command palette, file finder, etc.) with the keyboard and then hovering with the mouse, two items would appear highlighted at the same time - one from the keyboard selection ghost_element_selected background and one from the CSS style ghost_element_hover background. This happened because GPUI's CSS hover is applied at paint time independently from the Rust selection state. ### Solution Added a keyboard_navigated: bool flag to [Picker] to track whether the last navigation was via keyboard or mouse. When a keyboard navigation action fires (↑, ↓, Home, End, etc.), `keyboard_navigated` is set to true. A transparent `block_mouse_except_scroll` overlay div is added on top of each list item, preventing the CSS style from firing on `ListItem` children. When the mouse moves over any item, the overlay's `on_mouse_move` handler fires, clears keyboard_navigated, moves the selection to that item, and re-renders - removing all overlays and restoring normal mouse hover behavior. The `on_hover` handler also moves the selection to whichever item the mouse enters, so mouse navigation is fully functional. Clicks work through the overlay since `block_mouse_except_scroll` only affects hover hit-testing, not click events on the parent div. ### Behavior Matches VS Code / IntelliJ — keyboard navigation suppresses hover highlights until the mouse moves, at which point normal hover resumes. Only one item is ever highlighted at a time. ### Video [Screencast from 2026-03-05 19-20-16.webm](https://github.com/user-attachments/assets/7a8a543a-95f3-481f-b59d-171604e5f883) --------- Co-authored-by: Nathan Sobo <nathan@zed.dev>
Fixes #31865
Problem
When navigating a picker (command palette, file finder, etc.) with the keyboard and then hovering with the mouse, two items would appear highlighted at the same time - one from the keyboard selection ghost_element_selected background and one from the CSS style ghost_element_hover background. This happened because GPUI's CSS hover is applied at paint time independently from the Rust selection state.
Solution
Added a keyboard_navigated: bool flag to [Picker] to track whether the last navigation was via keyboard or mouse.
When a keyboard navigation action fires (↑, ↓, Home, End, etc.),
keyboard_navigatedis set to true. A transparentblock_mouse_except_scrolloverlay div is added on top of each list item, preventing the CSS style from firing onListItemchildren.When the mouse moves over any item, the overlay's
on_mouse_movehandler fires, clears keyboard_navigated, moves the selection to that item, and re-renders - removing all overlays and restoring normal mouse hover behavior.The
on_hoverhandler also moves the selection to whichever item the mouse enters, so mouse navigation is fully functional.Clicks work through the overlay since
block_mouse_except_scrollonly affects hover hit-testing, not click events on the parent div.Behavior
Matches VS Code / IntelliJ — keyboard navigation suppresses hover highlights until the mouse moves, at which point normal hover resumes. Only one item is ever highlighted at a time.
Video
Screencast.from.2026-03-05.19-20-16.webm