Skip to content

Make the rich text editor toolbars a single tab stop with arrow-key navigation - #6108

Open
rtibblesbot wants to merge 3 commits into
learningequality:unstablefrom
rtibblesbot:issue-6104-e657d5
Open

Make the rich text editor toolbars a single tab stop with arrow-key navigation#6108
rtibblesbot wants to merge 3 commits into
learningequality:unstablefrom
rtibblesbot:issue-6104-e657d5

Conversation

@rtibblesbot

@rtibblesbot rtibblesbot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Summary

A useRovingTabIndex composable keeps exactly one toolbar control at tabindex="0" and moves focus with Left/Right, wrapping at both ends and reversed in RTL. Unavailable controls are marked aria-disabled rather than disabled, so they keep their place in the arrow order. Wired into all four role="toolbar" elements in the editor: EditorToolbar, MobileTopBar, MobileFormattingBar and LinkBubbleMenu.

References

Fixes #6104. Pattern: WAI-ARIA APG toolbar.

Reviewer guidance

  1. Open a channel, edit an exercise, open the Questions tab, click a question, then the pencil on the Question field.
  2. Right from any control steps through the toolbar and wraps from the last to the first.
  3. Left on the first control lands on the minimize button.
  4. Enter and Space each open the "Normal" text-format menu.
  5. Left/Right step past that trigger while its menu is closed.
  6. Tab out and Shift+Tab back — focus returns to the control you left.
  7. Undo and Redo keep their place in the arrow order while greyed out, and do nothing when activated.
  8. Narrow the window to the mobile layout — the top bar navigates the same way.
  9. On a touch device, focus the text, then Shift+Tab — focus lands on the floating formatting bar, which is one tab stop and arrows the same way.
Toolbar with roving focus Arrow navigation and wrapping
Toolbar

MobileFormattingBar also needed a mount fix: it rendered only while the editor content held focus, so Tab blurred the content and unmounted the bar before focus could land in it.

AI usage

Used Claude Code to write the composable and its tests test-first against a pre-agreed plan, and to drive the toolbar in a browser for the capture above. Verified with the full Jest suite, pre-commit, an axe-core audit, and keyboard QA in Chromium.


@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly

How was this generated?
  • Ran pre-flight CI checks (lint, format, tests) and verified all pass
  • Rebased onto the target branch and resolved any conflicts
  • Reorganized commit history into clean, logical commits
  • Audited the diff to ensure only issue-relevant files are changed
  • Built PR body from the repository's PR template with evidence blocks
@rtibblesbot

🟡 Waiting for feedback

Last updated: 2026-08-27 15:18 UTC

@rtibblesbot
rtibblesbot force-pushed the issue-6104-e657d5 branch 5 times, most recently from 953bca2 to 2871664 Compare August 26, 2026 21:11
@rtibblesbot
rtibblesbot marked this pull request as ready for review August 26, 2026 21:16
rtibblesbot and others added 2 commits August 26, 2026 14:26
Roving tabindex over a container's `[data-toolbar-item]` controls, per
the WAI-ARIA APG toolbar pattern: one tab stop, Left/Right between
controls, wrapping and reversed in RTL.

Excludes controls KListWithOverflow has hidden via `visibility`, and
ignores arrow keys raised inside an open menu.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Mark every toolbar control `data-toolbar-item` and drive the toolbars
with useRovingTabIndex, so Tab moves into the toolbar and then out.

Unavailable ToolbarButtons carry `aria-disabled` instead of the native
`disabled`, keeping them focusable and in the arrow-key order.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@rtibblesbot rtibblesbot changed the title Make the rich text editor toolbar a single tab stop with arrow-key navigation Make the rich text editor toolbars a single tab stop with arrow-key navigation Aug 26, 2026

@AlexVelezLl AlexVelezLl left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hey @rtibblesbot, on mobile, the mobile toolbar is not reachable via tab, could you fix it please, and make that the corresponding tab position on the RTE toolbar goes to the mobile toolbar instead?

@rtibblesbot

Copy link
Copy Markdown
Contributor Author

@AlexVelezLl fixed in 1c54e96.

The floating formatting bar rendered only while the editor content held focus (v-if="isFocused"), so Tab blurred the content and unmounted the bar before focus could land in it. It now stays mounted while it holds focus, and carries the same roving tabindex as the other toolbars — one tab stop, Left/Right between controls, wrapping, reversed in RTL. Its +/- format buttons moved from disabled to aria-disabled so they keep their place in that order.

On the tab position: the bar sits between the top bar and the content, which is where the RTE toolbar sits on desktop — Shift+Tab from the text now lands on it. One caveat worth your call: the bar is still only rendered once the content has been focused, so a first forward Tab through the editor passes from the top bar straight to the text. Rendering it earlier would make it visible before the keyboard opens; say the word if you want that instead.

Class sweep: role="toolbar" matches 4 elements in the frontend — EditorToolbar and MobileTopBar were already wired, MobileFormattingBar and LinkBubbleMenu were not. Both are now, so the "left for a follow-up" note is gone from the PR body. I also checked every :disabled and tabindex binding under TipTapEditor/components/: the rest are modal submit buttons and role="menuitem" items, which own their own focus order and are correct as-is.

New specs cover the reachability fix (focus moves from the content into the bar and the bar survives), the single tab stop and arrow movement on both toolbars, and the unavailable format button keeping its place.


@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly

MobileFormattingBar unmounted on editor blur, so Tab could never land in it.
Keep it mounted while it holds focus, and give it and LinkBubbleMenu the same
single tab stop as the other two role="toolbar" elements.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

[RTE] Toolbar is not keyboard-navigable per the ARIA toolbar pattern

2 participants