feat: support focusable disabled collection items via allowFocusWhenDisabled#10278
Open
Wonchang0314 wants to merge 1 commit into
Open
feat: support focusable disabled collection items via allowFocusWhenDisabled#10278Wonchang0314 wants to merge 1 commit into
Wonchang0314 wants to merge 1 commit into
Conversation
…isabled MenuItem, ListBoxItem, Tab, and TreeItem previously could not implement the ARIA APG "focusability of disabled controls" pattern: a disabled item either got `aria-disabled` but was removed from keyboard navigation (disabledBehavior 'all'), or stayed navigable but dropped `aria-disabled` (disabledBehavior 'selection'). Neither produced a focusable item that is also marked `aria-disabled`. Closes adobe#10276. Add an opt-in per-item `allowFocusWhenDisabled` prop that decouples focusability from the existing disabled semantics. The item stays non-interactive (no selection or action, `aria-disabled` is still emitted) but remains reachable via roving tabindex / arrow-key navigation. `isDisabled` is intentionally left unchanged so `aria-disabled` emission and selection/action suppression keep working; only a new orthogonal focusability axis is introduced. - react-stately: add `MultipleSelectionManager.isFocusableWhenDisabled` (optional on the interface for external-implementer compat) backed by the item prop. - useSelectableItem: keep the roving tabindex/focus wiring and avoid clearing the focused key for focusable-disabled items. Native link navigation is already blocked by the existing unconditional link onClick preventDefault, since pressProps is never merged for a disabled item. - List/Grid/Tabs keyboard delegates: stop skipping focusable-disabled items. - useTab: don't null out the roving tabindex for a focusable-disabled tab. - react-aria-components: expose `allowFocusWhenDisabled` on MenuItem, ListBoxItem, Tab, and TreeItem. Tests cover, per component: aria-disabled stays set, the item is reachable via keyboard navigation, and selection/action remain blocked.
a7cb2a4 to
9dafa04
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #10276
📝 Summary
MenuItem,ListBoxItem,Tab, andTreeItemcould not implement the ARIA APG Focusability of disabled controls pattern. A disabled collection item was stuck between two behaviors:disabledBehavioraria-disabled'all'(default)'selection'Neither produces a focusable item that is also marked
aria-disabled(unlikeButton, which decouplesaria-disabledfrom focusability viaallowFocusWhenDisabled).This PR adds an opt-in, per-item
allowFocusWhenDisabledprop to these four components. The item stays fully non-interactive (no selection, no action,aria-disabledstill emitted) but remains reachable via roving tabindex / arrow-key navigation.🔧 Approach
A single
isDisabled(key)currently drives three concerns at once:aria-disabled, selection/action blocking, and removal from keyboard navigation. To support focusable-disabled items without changing any of that existing behavior, this PR leavesisDisableduntouched and adds one independent axis that only controls focusability:MultipleSelectionManager.isFocusableWhenDisabled(key), backed by the new item prop.useSelectableItem: keeps the roving tabindex/focus wiring for these items; selection/action stay blocked andaria-disabledstays set. (Native<a href>navigation is already prevented by the existing linkonClick, so no extra handling is needed.)useTab: stop skipping / un-tabbing focusable-disabled items.allowFocusWhenDisabledonMenuItem,ListBoxItem,Tab,TreeItem.The decoupling is inspired by base-ui's
focusableWhenDisabledand this library's ownButtonallowFocusWhenDisabled. Unlike base-ui, which makes composite items focusable-when-disabled by default, this is strictly opt-in so existing apps relying on disabled items being skipped are unaffected. (Type-to-select also reaches these items, consistent with them being focusable.)✅ Pull Request Checklist:
📝 Test Instructions:
Automated (all green):
Each component has new tests (covering both the item-level
isDisabledprop and collection-leveldisabledKeyspaths) asserting that an item withallowFocusWhenDisabled: (1) keepsaria-disabled="true", (2) is reachable via arrow-key navigation (not skipped), (3) cannot be selected or actioned.Manual:
Arrow-key down should land focus on "Rename"; a screen reader should announce it as disabled; pressing Enter should do nothing.
🧢 Your Project:
Community contribution