Skip to content

Fix Autocomplete focus styles when clicking the input after virtual focus#9767

Merged
devongovett merged 3 commits intoadobe:mainfrom
hanityx:codex/fix-autocomplete-focus-recovery
Mar 18, 2026
Merged

Fix Autocomplete focus styles when clicking the input after virtual focus#9767
devongovett merged 3 commits intoadobe:mainfrom
hanityx:codex/fix-autocomplete-focus-recovery

Conversation

@hanityx
Copy link
Contributor

@hanityx hanityx commented Mar 9, 2026

Closes #8198

Fixes the Autocomplete case where virtual focus moves to an option and clicking the already-focused input does not restore the input's focused state. This adds a small fix, regression tests, and a storybook repro for manual verification. No documentation changes seem necessary for this behavior fix.

✅ Pull Request Checklist:

  • Included link to corresponding React Spectrum GitHub Issue.
  • Added/updated unit tests and storybook for this change (for new code or code which already has tests).
  • Filled out test instructions.
  • Updated documentation (if it already exists for this component).
  • Looked at the Accessibility Practices for this feature - Aria Practices

📝 Test Instructions:

Automated:

  • yarn jest packages/react-aria-components/test/Autocomplete.test.tsx --runInBand
  • yarn jest packages/@react-aria/autocomplete/test/useSearchAutocomplete.test.js --runInBand
  • yarn jest packages/react-aria-components/test/ComboBox.test.js --runInBand
  • yarn jest packages/@react-aria/combobox/test/useComboBox.test.js --runInBand
  • yarn eslint packages/@react-aria/autocomplete/src/useAutocomplete.ts packages/react-aria-components/test/Autocomplete.test.tsx packages/react-aria-components/stories/Autocomplete.stories.tsx
  • yarn check-types

Manual:

  1. Run storybook with yarn start.
  2. Open React Aria Components/Autocomplete.
  3. Open Autocomplete focus recovery after virtual focus.
  4. Focus the input, then either hover an option or move focus with the keyboard.
  5. Click the input again.
  6. Verify that the input regains its focused styling and the active descendant is cleared.

🧢 Your Project:

Copilot AI review requested due to automatic review settings March 9, 2026 17:05
@github-actions github-actions bot added the RAC label Mar 9, 2026
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes an Autocomplete virtual-focus edge case where clicking an already-focused input doesn’t restore the input’s focused styling (and doesn’t clear aria-activedescendant) after virtual focus has moved to an option.

Changes:

  • Clear virtual focus on pointerdown when the input is already the active element (excluding touch), so focused styling is restored on click.
  • Add regression tests covering hover-driven and keyboard-driven virtual focus recovery scenarios.
  • Add a Storybook repro story for manual verification of focus recovery behavior.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
packages/@react-aria/autocomplete/src/useAutocomplete.ts Adds an onPointerDown handler (virtual-focus mode) to clear virtual focus when clicking an already-focused input.
packages/react-aria-components/test/Autocomplete.test.tsx Adds regression tests ensuring focused styles and aria-activedescendant are restored/cleared when clicking the input after virtual focus moves to options.
packages/react-aria-components/stories/Autocomplete.stories.tsx Adds a Storybook scenario to manually reproduce and validate focus recovery after virtual focus.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@hanityx hanityx closed this Mar 9, 2026
@hanityx hanityx reopened this Mar 9, 2026
@hanityx hanityx closed this Mar 9, 2026
@hanityx hanityx reopened this Mar 9, 2026
Copy link
Member

@snowystinger snowystinger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR. One oddity about this, the mouse shouldn't actually cause a visible focus ring usually. There should probably be a "data-focus-visible" condition to the css as well.

let menu = getByRole('menu');
let options = within(menu).getAllByRole('menuitem');
await user.hover(options[1]);
options = within(menu).getAllByRole('menuitem');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
options = within(menu).getAllByRole('menuitem');

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also removed that extra re-fetch.

return;
}

if (getEventTarget(e) === inputRef.current && getActiveElement(getOwnerDocument(inputRef.current)) === inputRef.current) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why the check for activeElement? I was trying out the change in https://react-aria.adobe.com/Autocomplete (though my local copy of yarn start:s2-docs and if I mouse over an option, then click outside the menu/input but still inside the dialog (so it doesn't dismiss), then I see that clicking on the input after that won't get the focus ring.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You were right here. I removed the activeElement check, added a regression test for the outside-click case, and switched the starter input ring to data-focus-visible.

snowystinger
snowystinger previously approved these changes Mar 16, 2026
Copy link
Member

@snowystinger snowystinger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That looks like it's behaving correctly. Thanks for updating that and the extra test!

Copy link
Member

@devongovett devongovett left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another concern is that this means the behavior of mouse vs keyboard is inconsistent. For mouse, if you click away and then back into the input, it would clear. But if you tab away and return it will not clear. Is this a problem? 🤷

// interactions restore the input state before the click's focus handling runs.
// Touch is excluded because touch interactions should not move focus back to the input.
let onPointerDown = (e: ReactPointerEvent) => {
if (e.button !== 0 || e.pointerType === 'touch' || queuedActiveDescendant.current == null || inputRef.current == null) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is touch excluded?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point. I kept it excluded because removing that guard changes touch behavior too - re-tapping would start clearing virtual focus on touch as well, and I wasn't sure that was intended.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked into making the return-to-input behavior consistently clear, but it affects more than this PR's original scope (virtual focus restoration, submenu/subdialog cases, etc).

Would it make sense to keep this PR focused on the original focus recovery / styling issue, and tackle the broader preserve-vs-clear question separately? I can follow up in either direction once the intended behavior is clear.

}

&[data-focused] {
&[data-focus-visible] {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should keep the styles unchanged. There should be a focus state even for mouse

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted this in 8c4d7f4 so the starter input keeps the existing mouse focus styling.

Copy link
Member

@devongovett devongovett left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving for testing

@devongovett devongovett added this pull request to the merge queue Mar 18, 2026
Merged via the queue into adobe:main with commit 891395b Mar 18, 2026
28 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Autocomplete input focus is inconsistent

6 participants