Skip to content

feat(ui5-toolbar): implement WAI-ARIA toolbar keyboard navigation#13622

Open
plamenivanov91 wants to merge 15 commits into
mainfrom
tb-arrow-nav-1-june
Open

feat(ui5-toolbar): implement WAI-ARIA toolbar keyboard navigation#13622
plamenivanov91 wants to merge 15 commits into
mainfrom
tb-arrow-nav-1-june

Conversation

@plamenivanov91

@plamenivanov91 plamenivanov91 commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Replace Tab-through-every-item with a proper arrow-key navigation
pattern per the WAI-ARIA toolbar pattern.

Keyboard handling

[Left]/[Right] navigate between toolbar items (RTL-aware). At the
first/last item, the keypress is absorbed and nothing happens (no wrap).
[Home]/[End] jump to first/last item, unless the focused item is
caret-aware (Input, TextArea), in which case the item handles the key.
[Tab]/[Shift+Tab] exit the toolbar — all items have tabIndex=0 so
the browser's natural tab order handles traversal out of the toolbar.
Overflow popover is fully isolated: arrow keys and focusin handling
are suppressed when the popover is open, leaving the popover's natural
Tab order intact.
On popover open, first interactive overflow item receives focus.

Tab navigation

All toolbar items are kept at tabIndex=0. Tab exits the toolbar
naturally without any roving tabIndex management.
_lastFocusedItem tracks the last focused item for re-entry (e.g.
Shift+Tab back into the toolbar) but does not change tabIndex values.
When the overflow button disappears (items move back to toolbar),
focus moves to the last navigable item via focusForToolbarNavigation.
Overflowed items are reset to tabIndex=0 so Tab works inside the
overflow popover.

ToolbarItemBase — hook API for complex items
New overridable methods (all no-ops in base):

getArrowNavState(): reports { atLeftEnd, atRightEnd } so the toolbar
knows when a Left/Right/Home/End press is at an internal boundary and
should be handled by the item rather than moving to the next item.
focusForToolbarNavigation(isForward): direction-aware focus entry.
setToolbarForcedTabIndex(tabIndex): distributes tabIndex to the item's
focus target.

ToolbarItem — three navigation paths

_itemNavigation introspection: components that use ItemNavigation
internally (e.g. SegmentedButton) are detected via duck-typing and
their currentIndex/itemCount is used to derive boundary state.
getArrowNavState interface: explicit opt-in for components that want
to expose caret/selection position as boundary info (e.g. Input).
Multi-child fallback: ToolbarItems with more than one slotted child
(radio button groups, checkbox groups) are treated as a navigable
group using _getCurrentNavigationState — no API needed on children.

Input / TextArea — caret-aware boundary
Both implement getArrowNavState() returning { atLeftEnd: caret===0,
atRightEnd: caret>=len } so Left/Right/Home/End only exits to the
next toolbar item once the caret is at the start or end of the text.

Templates
ToolbarButtonTemplate and ToolbarSelectTemplate: removed tabIndex JSX
prop — setting it on the UI5 host broke F6Navigation's focus traversal.

Docs

Updated keyboard handling JSDoc on ui5-toolbar.
Added WCAG 2.1 notes to accessibleName / accessibleNameRef.
Restored @csspart root JSDoc entry on ui5-radio-button.

Tests (Toolbar.cy.tsx)

Arrow Left/Right navigation between items.
No-wrap: ArrowLeft on first item and ArrowRight on last item do nothing.
Home/End jump to first/last.
Tab navigates between all items (all have tabIndex=0).
Input caret-aware: Left/Right exits toolbar only at caret boundary.
Home key not intercepted by toolbar when Input has focus.
First overflow item focused on popover open.
Checkbox group: arrow traversal within group + boundary exit.
Overflow button: ArrowLeft navigates to last visible toolbar item.
Updated "overflow button disappears" test to check focused
ToolbarButton directly instead of shadow .ui5-tb-item wrapper.

JIRA: BGSOFUIPIRIN-7018
Fixes: #12945

…2 (prevent page scroll on unhandled up/down)
Refactor toolbar keyboard handling around a single toolbar-owned flow.

- centralize arrow and tab navigation in Toolbar
- add movement-info and roving-tabindex hooks to ToolbarItemBase
- adapt grouped ToolbarItem content through shared internal target logic
- restore caret-aware movement for Input and TextArea
- apply forced tabindex to toolbar button/select templates
- remove redundant select-owned keyboard handling
- add Toolbar regressions for checkbox groups and overflow-button exit
@ui5-webcomponents-bot

ui5-webcomponents-bot commented Jun 2, 2026

Copy link
Copy Markdown
Collaborator

@ui5-webcomponents-bot ui5-webcomponents-bot temporarily deployed to preview June 2, 2026 08:48 Inactive
@ui5-webcomponents-bot ui5-webcomponents-bot temporarily deployed to preview June 2, 2026 09:45 Inactive
- Skip focusin/keydown handling when focus is inside the open overflow
  popover, preventing arrow-nav logic from firing inside the popover
- Skip forcedTabIndex on overflowed ToolbarButton/ToolbarSelect so
  overflow items keep their natural tab order
- Fix Tab-exit containment check to use shadow-DOM-aware walk
  (_isNodeInsideElement) instead of contains/shadowRoot.contains
- Remove own-fallback movement info path from ToolbarItem; items without
  _itemNavigation or getToolbarMovementInfo are now treated as single
  tab stops
- Drop dead WeakMap tab-index restoration machinery (no longer needed
  now that overflow items manage their own tab order)
@ui5-webcomponents-bot ui5-webcomponents-bot temporarily deployed to preview June 3, 2026 10:24 Inactive
@plamenivanov91 plamenivanov91 changed the title fix(ui5-toolbar): simplify toolbar item keyboard navigation feat(ui5-toolbar): implement WAI-ARIA toolbar keyboard navigation Jun 3, 2026
Comment thread packages/main/src/Toolbar.ts Outdated
Comment thread packages/main/src/ToolbarItem.ts Outdated
Comment thread packages/main/src/Toolbar.ts Outdated
Comment thread packages/main/src/RadioButton.ts
Comment thread packages/main/src/Toolbar.ts Outdated
@plamenivanov91 plamenivanov91 marked this pull request as ready for review June 10, 2026 12:02
@github-actions

Copy link
Copy Markdown

👋 Heads-up: dev close is in effect

Thanks for the contribution! This repository is currently in dev close ahead of release next (scheduled 2026-07-09, UTC). See the release schedule for the full timeline.

This PR appears to introduce public-API changes (detected by diffing the Custom Elements Manifest against the latest published version on npm):

@ui5/webcomponents

  • ➕ added interface: IInputSuggestionItem
  • ➕ added interface: IToolbarItemContent
  • 🔄 changed attribute value-state (type)
  • 🔄 changed attribute value-state (type)

Could you please hold off on merging into main until the release ships? Public-API changes are best landed in the next dev cycle so they don't slip into the release at the last minute. Once the release is out, this PR is good to go.

If this change must ship in the current release, please request a review from one or two members of @UI5/ui5-team-webc so the team can sign off explicitly.

💬 False positive? If you believe this PR doesn't actually change the public API (e.g. only internal refactoring, or an entry the detector mis-attributed), please reply on this thread — your feedback helps us improve the detection during this trial run.

Posted automatically by the Dev Close Notice workflow.

…avState interface

Replace the index-based ToolbarMovementInfo interface with a boolean
ToolbarArrowNavState { atLeftEnd, atRightEnd }. Items now report
whether they can absorb a keypress rather than exposing their internal
item count and cursor position.

- ToolbarItemBase/Item: remove ToolbarMovementInfo, ToolbarMovementEnabler,
  moveWithinToolbarItem. Add getArrowNavState() returning
  { atLeftEnd, atRightEnd } (delegates to single child if present,
  falls back to multi-child position check). Remove dead no-op branch
  in focusForToolbarNavigation().
- Input/TextArea: getToolbarMovementInfo ? getArrowNavState, returning
  { atLeftEnd: caret===0, atRightEnd: caret>=len }.
- Toolbar: _applyRovingTabIndex sets all items to tabIndex=0 (Tab exits
  naturally). _setCurrentItem only tracks _lastFocusedItem, no tabIndex
  manipulation. Arrow nav guards via getArrowNavState(). Home/End also
  guarded so caret-aware items (Input, TextArea) can handle them
  internally. _moveToNext/_moveToPrev clamped at boundaries, no wrap.
- Tests: add no-wrap boundary, Tab navigation, Input caret-aware nav,
  and Home key non-interception tests. Fix ToolbarSelect tests using
  incorrect "click" event name and jQuery .get(0) anti-pattern.
@plamenivanov91 plamenivanov91 deployed to netlify-preview June 30, 2026 12:55 — with GitHub Actions Active
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.

[ui5-toolbar][A11y]: toolbar keyboard interaction does not align with WCAG requirement

3 participants