Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement keyboard arrow navigation for certain components #116

Open
mkrause opened this issue Jan 22, 2025 · 1 comment
Open

Implement keyboard arrow navigation for certain components #116

mkrause opened this issue Jan 22, 2025 · 1 comment
Labels
Milestone

Comments

@mkrause
Copy link
Collaborator

mkrause commented Jan 22, 2025

There are certain "composite" components, where for accessibility purposes, we want the main component to be focusable, and then the user can use arrow keys to navigate between the various subcomponents, and space bar to select one. Currently, we have implemented this a bit differently, where each subcomponent is focusable on its own. However, this goes against accessibility best practices.

This applies, to at least:

  • DropdownMenu
  • Select
  • RadioGroup
  • SegmentedControl
  • Tabs
  • Stepper

See for example how native HTML <select> works. You can focus the <select>, hit space to open the dropdown, and then use arrow keys to navigate to some option and then space bar again to select it. Hitting tab during this process will never highlight an individual dropdown option. To emulate this with a custom select element, see the accessibility guidelines here:

https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/listbox_role
https://www.w3.org/WAI/ARIA/apg/patterns/listbox/examples/listbox-scrollable

@mkrause
Copy link
Collaborator Author

mkrause commented Jan 22, 2025

Quick and dirty experiment in DropdownMenu.tsx:

// DropdownMenu.tsx

// on the <Button> elements:
tabIndex={-1} // Only the `role="listbox"` should be focusable, use keyboard arrows to select the option


//...

    const { selectedOption, selectOption } = useDropdownMenuContext();


    const handleKeyInput = React.useCallback((event: React.KeyboardEvent) => {
      if (event.key === 'ArrowUp') {
        event.preventDefault();
        const optionKey = selectedOption === null ? 'option-1' : `${selectedOption.slice(0, -1)}${Number(selectedOption.slice(-1)) - 1}`;
        selectOption({ optionKey, label: 'Test' });
        event.target.querySelector(`[data-option-key="${optionKey}"]`)?.scrollIntoView({
          behavior: 'smooth',
          block: 'nearest',
        });
      } else if (event.key === 'ArrowDown') {
        event.preventDefault();
        const optionKey = selectedOption === null ? 'option-1' : `${selectedOption.slice(0, -1)}${Number(selectedOption.slice(-1)) + 1}`;
        selectOption({ optionKey, label: 'Test' });
        event.target.querySelector(`[data-option-key="${optionKey}"]`)?.scrollIntoView({
          behavior: 'smooth',
          block: 'nearest',
        });
      }
    }, [selectedOption]);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant