Skip to content

fix(Dropdown): a11y issues with keyboard behavior#1104

Open
kheinrich-eightfold wants to merge 5 commits intomainfrom
kheinrich/dropdown-keyboard-nav-fixes
Open

fix(Dropdown): a11y issues with keyboard behavior#1104
kheinrich-eightfold wants to merge 5 commits intomainfrom
kheinrich/dropdown-keyboard-nav-fixes

Conversation

@kheinrich-eightfold
Copy link
Contributor

@kheinrich-eightfold kheinrich-eightfold commented Mar 5, 2026

SUMMARY:

This PR fixes Dropdown keyboard behavior to align with WAI-ARIA menu button practices.

Issues fixed

  • Tab behavior when menu is open: Tab should close the menu and move focus to the next focusable element. It was closing but focus went back to the trigger. Now we call preventDefault() and, after close, focus the next focusable in document order when shouldCloseOnTab is true.
  • Arrow Up when trigger is focused: Arrow Up on the closed trigger should open the menu and focus the last item. It did nothing before. Now we open the menu and move focus to the last focusable element.
  • Escape: Focus did not reliably return to the trigger when pressing Escape. Escape (and other close paths) now set focusTargetAfterCloseRef to the trigger; the shared “on close” effect runs this so focus always returns.
  • Shift+Tab: Default toggleDropdownOnShiftTab is now false (close when focus leaves via Shift+Tab). When shouldCloseOnTab is true, Shift+Tab closes and returns focus to the trigger. toggleDropdownOnShiftTab={true} is deprecated.

Refactoring

  • Focus-after-close: One effect runs when mergedVisible becomes false and, if set, invokes focusTargetAfterCloseRef.current?.() then clears it. Tab, Shift+Tab, Escape, and outside/trigger click all set this ref.
  • Refs: closedByTabRef, focusLastOnOpenRef, and focusTargetAfterCloseRef record close/open intent so the effect picks the right focus target.
  • Helpers: getOverlayFocusableItems(), getDocumentFocusableElements(), getNextFocusableAfterReference(), hasFocusWithin(), focusUntilActive() / focusFirstElement() / focusLastElement() reduce duplication.
  • Cleanup: Focus interval cleared in a useEffect cleanup on unmount.

Testing & docs

  • New/updated unit tests for Tab (next focusable), Arrow Up (open + focus last), Escape (trigger refocus, including controlled), and Shift+Tab (default close vs. toggleDropdownOnShiftTab).
  • Story Dropdown_Button_KeyboardFocus for manual Tab/Shift+Tab and focus verification.

GITHUB ISSUE (Open Source Contributors)

JIRA TASK (Eightfold Employees Only):

https://eightfoldai.atlassian.net/browse/ENG-162951
https://eightfoldai.atlassian.net/browse/ENG-163088

CHANGE TYPE:

  • Bugfix Pull Request
  • Feature Pull Request

TEST COVERAGE:

  • Tests for this change already exist
  • I have added unittests for this change

TEST PLAN:

Testing in vscode sandbox (recommended)

Test 1 - Account dropdown should follow Menu Button pattern (ENG-162951)

  1. Spin up a sandbox for this PR (includes Octuple changes): https://github.com/EightfoldAI/vscode/pull/102152
  2. From the sandbox base URL, go to /careers?domain=eightfolddemo-levelaccess.com
  3. Log in / Sign up to see the account dropdown menu in the top navbar
  4. Ensure the account dropdown implements the following keyboard behavior for the Menu Button pattern:

When focus is on the trigger button:

  • Enter / Space: Opens the menu and moves focus to the first item
  • Down Arrow: Opens the menu and focuses the first item
  • Up Arrow: Opens the menu and focuses the last item
  • Tab: Moves focus away (menu stays closed)

When focus is on a menu item inside the popup:

  • Up / Down Arrow: Move between menu items
  • Enter / Space: Activate the focused menu item
  • Esc: Close the menu and return focus to the trigger button
  • Tab: Close the menu and move to the next focusable element on the page

Test 2 - Dates dropdown should have correct focus management (ENG-163088)

  1. Spin up a sandbox for this PR (includes Octuple changes): https://github.com/EightfoldAI/vscode/pull/102152
  2. Go to /events/open?domain=eightfolddemo-levelaccess.com
  3. Locate the "All dates" control on the right side of the Events listing page header.
  4. Ensure the dates dropdown has correct Tab bahavior when focus is inside the popup: pressing Tab should close the popup and move focus to the next focusable element on the page (not return to the trigger button).

Testing in Octuple Storybook

  • Pull branch and run yarn storybook
  • Open Storybook and navigate to the "Dropdown Button Keyboard Focus" story
  • Ensure the dropdown has all of the following expected keyboard behaviors for accessibility:
    • When focus is on the Dropdown trigger button:
      • Enter / Space: Opens the menu and moves focus to the first item
      • Down Arrow: Opens the menu and focuses the first item
      • Up Arrow: Opens the menu and focuses the last item
      • Tab: Moves focus away (menu stays closed)
    • When focus is inside the Dropdown overlay:
      • Up / Down Arrow: Move between menu items
      • Enter / Space: Activate the focused menu item
      • Esc: Close the menu and return focus to the trigger button
      • Tab: Close the menu and move to the next focusable element

@codesandbox-ci
Copy link

codesandbox-ci bot commented Mar 5, 2026

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

@codecov
Copy link

codecov bot commented Mar 5, 2026

Codecov Report

❌ Patch coverage is 91.07143% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.24%. Comparing base (66efd17) to head (ca4758c).

Files with missing lines Patch % Lines
src/components/Dropdown/Dropdown.tsx 91.07% 10 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1104      +/-   ##
==========================================
+ Coverage   84.14%   84.24%   +0.10%     
==========================================
  Files        1146     1146              
  Lines       21179    21248      +69     
  Branches     8053     8069      +16     
==========================================
+ Hits        17821    17901      +80     
+ Misses       3271     3260      -11     
  Partials       87       87              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@kheinrich-eightfold kheinrich-eightfold force-pushed the kheinrich/dropdown-keyboard-nav-fixes branch 4 times, most recently from 8ec7d73 to 5f55835 Compare March 9, 2026 23:07
@kheinrich-eightfold kheinrich-eightfold marked this pull request as ready for review March 9, 2026 23:08
Copy link
Contributor

@factory-droid factory-droid bot left a comment

Choose a reason for hiding this comment

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

PR Description:
This PR enhances the Dropdown component's keyboard accessibility by implementing WAI-ARIA compliant navigation behaviors. Key changes include proper Tab key handling (closing dropdown and moving to next element), Arrow Up navigation (opening dropdown and focusing last item), and deprecation of toggleDropdownOnShiftTab. The implementation adds focus management utilities, state tracking via refs, comprehensive tests, and a new Storybook example for keyboard navigation testing.

Review:
LGTM

Open in Factory.
Click For Summary of Files

Summary of Files

Files Modified

src/components/Dropdown/Dropdown.test.tsx
Enhanced Dropdown component test coverage for keyboard accessibility:

- Added test cases for keyboard navigation behaviors:
  - Tab/Shift+Tab focus management and dropdown closing
  - Arrow Up/Down for opening dropdown and focusing first/last items
  - Focus handling when closing dropdown via keyboard
- Added test components to verify accessibility props:
  - shouldCloseOnTab behavior
  - toggleDropdownOnShiftTab functionality
  - initialFocus control
- Improved test implementation with proper animation timing and focus management
src/components/Dropdown/Dropdown.types.ts
Updated toggleDropdownOnShiftTab prop documentation and behavior for improved accessibility:

- Changed default value from true to false to align with WAI-ARIA standards
- Enhanced property documentation to clarify behavior with Shift+Tab key interaction
- Added deprecation notice for non-compliant usage (passing true)
src/components/Dropdown/Dropdown.tsx
Enhanced keyboard navigation and focus management in Dropdown component:

- Implemented WAI-ARIA compliant keyboard behaviors:
  - Arrow Up opens dropdown and focuses last item
  - Tab closes dropdown and moves to next element
  - Shift+Tab returns focus to trigger element
- Added robust focus management utilities with timeout-based focus scheduling
- Improved cleanup handling for focus-related timeouts and intervals
src/components/Dropdown/Dropdown.stories.tsx
Added keyboard accessibility demonstration story for Dropdown component:

- Created new Dropdown_Button_KeyboardFocus story showcasing WAI-ARIA compliant keyboard navigation
- Removed deprecated toggleDropdownOnShiftTab property and introduced shouldCloseOnTab for improved tab key handling
- Added focus visibility configuration and visual feedback through rotating chevron icons
Tips
Review Droid is highly customizable and comes with powerful features for augmenting your organization's code review process. Here are some tips to get the most out of it.

Table of contents

⌨️ Droid Fill

Contextual PR Body Replacement

When you create a PR with the @droid fill command anywhere in your PR body, Review Droid will fill in the PR description for your pull request based on it's PR analysis. This will also take into account your pull request templates.

Review Droid can also analyze your project management system. If you have a project management system integrated with Factory (e.g. Linear, Jira) Review Droid will also integrate information from linked and related tickets.

At Factory, we typically create our PR's with this command. For example, let's say I'm creating a PR which addresses the jira ticket FAC-123. I would write the following PR description:

@droid fill FAC-123

and your Review Droid fills in the rest!

📚 Review Guidelines

Creating guidelines for Droid to follow

You can configure guidelines that Droid will follow when reviewing your PRs. Droid will focus on these aspects of your code and aim to leave in-line comments if any guidelines are violated.

Guidelines are defined in your repository's .droid.yaml. Every week, Droid will automatically refine and edit these guidelines based on the feedback you leave on Droid's comments.

💬 Droid Chat

Ask questions on a PR

You can leave in-line comments on PR's by tagging @droid in-line. This can be helpful when reviewing other's PRs. Some examples include:

  • @droid this section looks sketchy, are there issues with it?
  • @droid can you show me some examples of what this regex matches?
  • @droid is this the most efficient way to do this? I'm concerned about performance.

Follow up with Review Droid's Comments

You can reply to Review Droid's in-line review comments directly to ask questions or provide feedback. Some examples include:

  • @droid I made the change you suggested, does that fix the issue?
  • @droid we don't actually need to do this because of X, Y, Z. Can you confirm?
  • @droid do we have any scripts that rely on this behavior?

🛠️ PR Healing

Diagnose & Fix Failures in CI

Review Droid is aware of the CI processes you utilize and proposes fixes in case of any failures. This allows it to promptly address issues in your pull requests before they escalate.

By default, PR Healing is activated. Your organization does not have advanced PR healing enabled, which involved Review Droid directly making a PR to your PR which fixes the issue. If you would like to enable this feature, you must have an Enterprise Plan.

🎓 Teaching Droid

Giving Droid feedback so it learns

You can give feedback to Review Droid by replying or reacting to its comments (👍 / 👎). This helps Review Droid learn from your preferences and improve its future reviews.

To send feedback directly to the Factory team, include @droid feedback in your comment. Droid will file a ticket with your feedback and provide a ticket ID so you can track it with our support team.

🔎 Review Usage

Re-Requesting Review

If you make changes to your PR and want Review Droid to re-review it, you can simply comment @droid review on the PR. This will trigger Droid to re-review the PR and update the review body.

.droid.yaml to Configure Review Droid

You can place a .droid.yaml file in the root of your repository. This file contains settings for a variety of features and settings including:

  • Guidelines - For defining the rules that Review Droid will enforce
  • Enabling/Disabling Per-file Summaries
  • Enabling/Disabling PR Healing
  • Path Filters (For ignoring certain files or directories)
  • Auto-Review Settings
  • Chat settings

To override a setting leave a comment on a PR with the setting to disable/enable/reset. For example @droid setting disable progress_comment. The current options are: progress_comment, lgtm_comment, and list.

list is a special setting that will list all the settings that you have set and will explain what each setting does.

For more information, you can view our documentation at https://docs.factory.ai - the password is factory.

Ignoring Reviews

If you want to have your PR ignored by Review Droid you can define Droid Ignored Title Words in your .droid.yaml file. If the title of your PR contains any of these words, Review Droid will ignore the PR.

Your organization currently has the following words in the Droid Ignored Title Words list:
None

@kheinrich-eightfold kheinrich-eightfold force-pushed the kheinrich/dropdown-keyboard-nav-fixes branch 3 times, most recently from cacd1a6 to b605bcc Compare March 16, 2026 01:04
@ychhabra-eightfold ychhabra-eightfold changed the title Fix a11y issues with Dropdown keyboard behavior fix(Dropdown): a11y issues with keyboard behavior Mar 17, 2026
@kheinrich-eightfold kheinrich-eightfold force-pushed the kheinrich/dropdown-keyboard-nav-fixes branch from b605bcc to 0816f27 Compare March 20, 2026 02:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant